├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── docker │ ├── Dockerfile │ └── README.md ├── java │ └── com │ │ └── company │ │ └── project │ │ ├── Application.java │ │ ├── configurer │ │ ├── MybatisConfigurer.java │ │ └── WebMvcConfigurer.java │ │ ├── core │ │ ├── constant │ │ │ └── ProjectConstant.java │ │ ├── exception │ │ │ └── ServiceException.java │ │ ├── mapper │ │ │ └── Mapper.java │ │ ├── result │ │ │ ├── Result.java │ │ │ ├── ResultCode.java │ │ │ └── ResultGenerator.java │ │ ├── service │ │ │ ├── AbstractService.java │ │ │ ├── AsyncService │ │ │ │ ├── AsyncConfig.java │ │ │ │ ├── AsyncNeedResponseService.java │ │ │ │ └── AsyncNoResponseService.java │ │ │ ├── Service.java │ │ │ ├── amqp │ │ │ │ ├── RabbitMQConfig.java │ │ │ │ ├── Receiver.java │ │ │ │ └── Sender.java │ │ │ ├── dubbo │ │ │ │ ├── City.java │ │ │ │ ├── CityDubboService.java │ │ │ │ └── impl │ │ │ │ │ └── CityDubboServiceImpl.java │ │ │ ├── dynProps4Files │ │ │ │ └── DynProps4FilesService.java │ │ │ ├── redis │ │ │ │ ├── RedisResult.java │ │ │ │ ├── RedisService.java │ │ │ │ └── impl │ │ │ │ │ ├── RedisKeyHelper.java │ │ │ │ │ └── RedisServiceImpl.java │ │ │ └── zookeeper │ │ │ │ ├── MachineNode.java │ │ │ │ ├── ZkManager.java │ │ │ │ ├── impl │ │ │ │ └── DomiZkManager.java │ │ │ │ └── watcher │ │ │ │ ├── DomiChildrenNodeHandler.java │ │ │ │ ├── DomiNodeHandler.java │ │ │ │ └── DomiZkEventWatcher.java │ │ └── utils │ │ │ ├── ProjectByteUtils.java │ │ │ ├── ProjectDateUtils.java │ │ │ ├── ProjectMD5Utils.java │ │ │ ├── ProjectPlatTraceUtils.java │ │ │ └── ProjectVerifyUtils.java │ │ ├── dao │ │ └── UserMapper.java │ │ ├── model │ │ ├── BaseDO.java │ │ └── User.java │ │ ├── service │ │ ├── UserService.java │ │ └── impl │ │ │ └── UserServiceImpl.java │ │ └── web │ │ └── UserController.java └── resources │ ├── application-dev.properties │ ├── application-pro.properties │ ├── application-test.properties │ ├── application.properties │ ├── banner.txt │ ├── log4j.xml │ ├── mapper │ └── UserMapper.xml │ └── templates │ └── welcome.ftl └── test ├── java ├── CodeGenerator.java └── com │ └── company │ └── project │ ├── Tester.java │ ├── amqp │ └── amqpTest.java │ ├── async │ ├── asyncNeedResponseTest.java │ └── asyncNoResponseTest.java │ ├── core │ └── service │ │ └── dubbo │ │ ├── City.java │ │ ├── CityDubboService.java │ │ └── DubboConsumerTest.java │ ├── mail │ └── code.txt │ └── redis │ └── redisTest.java └── resources ├── application-dev.properties ├── application.properties ├── demo-user.sql └── generator └── template ├── controller-restful.ftl ├── controller.ftl ├── service-impl.ftl └── service.ftl /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | ### STS ### 4 | .apt_generated 5 | .classpath 6 | .factorypath 7 | .project 8 | .settings 9 | .springBeans 10 | 11 | ### IntelliJ IDEA ### 12 | .idea 13 | *.iws 14 | *.iml 15 | *.ipr 16 | 17 | ### NetBeans ### 18 | nbproject/private/ 19 | build/ 20 | nbbuild/ 21 | dist/ 22 | nbdist/ 23 | .nb-gradle/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Licence](https://img.shields.io/badge/licence-none-green.svg) 2 | [![GitHub Release](https://img.shields.io/github/release/lihengming/spring-boot-api-project-seed.svg)](https://github.com/lihengming/spring-boot-api-project-seed/releases) 3 | ## 简介 4 | Spring Boot API Project Seed 是一个基于Spring Boot & MyBatis的种子项目,用于快速构建中小型API、RESTful API项目,该种子项目已经有过多个真实项目的实践,稳定、简单、快速,使我们摆脱那些重复劳动,专注于业务代码的编写,减少加班。下面是一个简单的使用演示,看如何基于本项目在短短几十秒钟内实现一套简单的API,并运行提供服务。 5 | 6 | [![请选择超清](https://raw.githubusercontent.com/lihengming/java-codes/master/shared-resources/github-images/project-example-youku.png)](http://v.youku.com/v_show/id_XMjg1NjYwNDgxNg==.html?spm=a2h3j.8428770.3416059.1) 7 | ## 特征&提供 8 | - 最佳实践的项目结构、配置文件、精简的POM([查看项目结构图](https://github.com/lihengming/java-codes/blob/master/shared-resources/github-images/project-struct.png)) 9 | - 统一响应结果封装及生成工具 10 | - 统一异常处理 11 | - 简单的接口签名认证 12 | - 常用基础方法抽象封装 13 | - 使用Druid Spring Boot Starter 集成Druid数据库连接池与监控 14 | - 使用FastJsonHttpMessageConverter,提高JSON序列化速度 15 | - 集成MyBatis、通用Mapper插件、PageHelper分页插件,实现单表业务零SQL 16 | - 提供代码生成器根据表名生成对应的Model、Mapper、MapperXML、Service、ServiceImpl、Controller等基础代码,其中Controller模板默认提供POST和RESTful两套,根据需求在```CodeGenerator.genController(tableName)```方法中自己选择,默认使用POST模板。代码模板可根据实际项目的需求来扩展,由于每个公司业务都不太一样,所以只提供了一些比较基础、通用的模板,主要是提供一个思路来减少重复代码的编写,我在实际项目的使用中,其实根据公司业务的抽象编写了大量的模板。另外,使用模板也有助于保持团队代码风格的统一 17 | - 另有彩蛋,待你探索 18 |   19 | ## 快速开始 20 | 1. 克隆项目 21 | 2. 对```test```包内的代码生成器```CodeGenerator```进行配置,主要是JDBC,因为要根据表名来生成代码 22 | 3. 如果只是想根据上面的演示来亲自试试的话可以使用```test resources```目录下的```demo-user.sql```,否则忽略该步 23 | 3. 输入表名,运行```CodeGenerator.main()```方法,生成基础代码(可能需要刷新项目目录才会出来) 24 | 4. 根据业务在基础代码上进行扩展 25 | 5. 对开发环境配置文件```application-dev.properties```进行配置,启动项目,Have Fun! 26 |   27 | ## 开发建议 28 | - 表名,建议使用小写,多个单词使用下划线拼接 29 | - Model内成员变量建议与表字段数量对应,如需扩展成员变量(比如连表查询)建议创建DTO,否则需在扩展的成员变量上加```@Transient```注解,详情见[通用Mapper插件文档说明](https://mapperhelper.github.io/docs/2.use/) 30 | - 建议业务失败直接使用```ServiceException("message")```抛出,由统一异常处理器来封装业务失败的响应结果,比如```throw new ServiceException("该手机号已被注册")```,会直接被封装为```{"code":400,"message":"该手机号已被注册"}```返回,无需自己处理,尽情抛出 31 | - 需要工具类的话建议先从```apache-commons-*```和```guava```中找,实在没有再造轮子或引入类库,尽量精简项目 32 | - 开发规范建议遵循阿里巴巴Java开发手册([最新版下载](https://github.com/lihengming/java-codes/blob/master/shared-resources/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8CV1.2.0.pdf)) 33 | - 建议在公司内部使用[ShowDoc](https://github.com/star7th/showdoc)、[SpringFox-Swagger2](https://github.com/springfox/springfox) 、[RAP](https://github.com/thx/RAP)等开源项目来编写、管理API文档 34 |   35 | ## 技术选型&文档 36 | - Spring Boot([查看Spring Boot学习&使用指南](http://www.jianshu.com/p/1a9fd8936bd8)) 37 | - MyBatis([查看官方中文文档](http://www.mybatis.org/mybatis-3/zh/index.html)) 38 | - MyBatisb通用Mapper插件([查看官方中文文档](https://mapperhelper.github.io/docs/)) 39 | - MyBatis PageHelper分页插件([查看官方中文文档](https://pagehelper.github.io/)) 40 | - Druid Spring Boot Starter([查看官方中文文档](https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter/)) 41 | - Fastjson([查看官方中文文档](https://github.com/Alibaba/fastjson/wiki/%E9%A6%96%E9%A1%B5)) 42 | - 其他略 43 | 44 | ## License 45 | 无,纯粹开源分享,感谢大家 [Star](https://github.com/lihengming/spring-boot-api-project-seed/stargazers) & [Fork](https://github.com/lihengming/spring-boot-api-project-seed/network/members) 的支持。 46 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.jimersylee 8 | spring-boot-api-project-seed 9 | 1.0 10 | jar 11 | 12 | 13 | 1.7 14 | jimersylee 15 | 16 | 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-parent 21 | 1.5.4.RELEASE 22 | 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-jdbc 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | 41 | commons-codec 42 | commons-codec 43 | 44 | 45 | org.apache.commons 46 | commons-lang3 47 | 3.5 48 | 49 | 50 | com.google.guava 51 | guava 52 | 22.0 53 | 54 | 55 | 56 | mysql 57 | mysql-connector-java 58 | runtime 59 | 60 | 61 | 62 | org.mybatis 63 | mybatis-spring 64 | 1.3.1 65 | 66 | 67 | org.mybatis 68 | mybatis 69 | 3.4.4 70 | 71 | 72 | tk.mybatis 73 | mapper 74 | 3.4.0 75 | 76 | 77 | com.github.pagehelper 78 | pagehelper 79 | 4.2.1 80 | 81 | 82 | 83 | com.alibaba 84 | fastjson 85 | 1.2.22 86 | 87 | 88 | 89 | com.alibaba 90 | druid-spring-boot-starter 91 | 1.1.2 92 | 93 | 94 | 95 | org.freemarker 96 | freemarker 97 | 2.3.23 98 | test 99 | 100 | 101 | org.mybatis.generator 102 | mybatis-generator-core 103 | 1.3.5 104 | test 105 | 106 | 107 | 108 | 109 | 110 | org.apache.zookeeper 111 | zookeeper 112 | 3.4.10 113 | 114 | 115 | org.slf4j 116 | slf4j-log4j12 117 | 118 | 119 | 120 | 121 | 122 | 123 | org.codehaus.jackson 124 | jackson-mapper-asl 125 | 1.9.13 126 | 127 | 128 | org.codehaus.jackson 129 | jackson-core-asl 130 | 1.9.13 131 | 132 | 133 | 134 | 135 | 136 | 137 | net.logstash.logback 138 | logstash-logback-encoder 139 | 4.11 140 | 141 | 142 | 143 | 144 | 145 | commons-lang 146 | commons-lang 147 | 2.6 148 | 149 | 150 | commons-io 151 | commons-io 152 | 2.2 153 | 154 | 155 | 156 | 157 | 158 | redis.clients 159 | jedis 160 | 2.8.1 161 | 162 | 163 | 164 | 165 | org.springframework.boot 166 | spring-boot-starter-amqp 167 | 168 | 169 | 170 | 171 | org.springframework.boot 172 | spring-boot-starter-logging 173 | 174 | 175 | 176 | 180 | 181 | 182 | 183 | org.springframework.boot 184 | spring-boot-starter-mail 185 | 186 | 187 | 188 | 189 | org.springframework.boot 190 | spring-boot-starter-actuator 191 | 192 | 193 | 194 | 195 | 196 | io.dubbo.springboot 197 | spring-boot-starter-dubbo 198 | 1.0.0 199 | 200 | 201 | 202 | 203 | org.springframework.boot 204 | spring-boot-configuration-processor 205 | true 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 218 | 219 | org.springframework.boot 220 | spring-boot-maven-plugin 221 | 222 | 223 | 224 | maven-compiler-plugin 225 | 226 | ${java.version} 227 | ${java.version} 228 | UTF-8 229 | 230 | 231 | 232 | 233 | 234 | 239 | 240 | com.spotify 241 | docker-maven-plugin 242 | 0.4.3 243 | 244 | ${docker.image.prefix}/${project.artifactId} 245 | src/main/docker 246 | 247 | 248 | / 249 | ${project.build.directory} 250 | ${project.build.finalName}.jar 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | aliyun-repos 264 | http://maven.aliyun.com/nexus/content/groups/public/ 265 | 266 | false 267 | 268 | 269 | 270 | 271 | 272 | 273 | aliyun-plugin 274 | http://maven.aliyun.com/nexus/content/groups/public/ 275 | 276 | false 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM frolvlad/alpine-oraclejdk8:slim 2 | VOLUME /tmp 3 | ADD spring-boot-api-project-seed-1.0.jar app.jar 4 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] -------------------------------------------------------------------------------- /src/main/docker/README.md: -------------------------------------------------------------------------------- 1 | # 安装docker环境 2 | 3 | 4 | deepin 是类似ubuntu的基于debian的系统,因此ubuntu安装类似 5 | 6 | 1.安装 7 | ``` 8 | sudo apt update 9 | sudo apt install docker.io -y 10 | ``` 11 | 2.配置链接 12 | ``` 13 | sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker 14 | sudo sed -i '$acomplete -F _docker docker' /etc/bash_completion.d/docker.io 15 | ``` 16 | 3.配置服务 17 | 18 | ``` 19 | sudo systemctl unmask docker.service 20 | sudo systemctl unmask docker.socket 21 | ``` 22 | 4.启动服务 23 | ``` 24 | sudo service docker start 25 | 或者 26 | sudo systemctl start docker 27 | ``` 28 | 5.运行hello world 29 | ``` 30 | sudo docker pull hello-world 31 | sudo docker run hello-world 32 | ``` 33 | 34 | 6.将应用打包 35 | ``` 36 | sudo mvn package docker:build -DskipTests 37 | ``` 38 | 39 | 7.在本地8080端口起服务 40 | ``` 41 | #通过终端展示调试信息 42 | sudo docker run -p 8080:8080 -t jimersylee/spring-boot-api-project-seed 43 | #不展示信息 44 | sudo docker run -p 8080:8080 jimersylee/spring-boot-api-project-seed 45 | 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/Application.java: -------------------------------------------------------------------------------- 1 | package com.company.project; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | 7 | @SpringBootApplication 8 | public class Application { 9 | 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(Application.class, args); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/configurer/MybatisConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.company.project.configurer; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import org.apache.ibatis.plugin.Interceptor; 5 | import org.apache.ibatis.session.SqlSessionFactory; 6 | import org.mybatis.spring.SqlSessionFactoryBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 10 | import org.springframework.core.io.support.ResourcePatternResolver; 11 | import tk.mybatis.spring.mapper.MapperScannerConfigurer; 12 | 13 | import javax.sql.DataSource; 14 | import java.util.Properties; 15 | 16 | import static com.company.project.core.constant.ProjectConstant.*; 17 | 18 | /** 19 | * Mybatis & Mapper & PageHelper 配置 20 | */ 21 | @Configuration 22 | public class MybatisConfigurer { 23 | 24 | @Bean 25 | public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception { 26 | SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); 27 | factory.setDataSource(dataSource); 28 | factory.setTypeAliasesPackage(MODEL_PACKAGE); 29 | 30 | //配置分页插件,详情请查阅官方文档 31 | PageHelper pageHelper = new PageHelper(); 32 | Properties properties = new Properties(); 33 | properties.setProperty("pageSizeZero", "true");//分页尺寸为0时查询所有纪录不再执行分页 34 | properties.setProperty("reasonable", "true");//页码<=0 查询第一页,页码>=总页数查询最后一页 35 | properties.setProperty("supportMethodsArguments", "true");//支持通过 Mapper 接口参数来传递分页参数 36 | pageHelper.setProperties(properties); 37 | 38 | //添加插件 39 | factory.setPlugins(new Interceptor[]{pageHelper}); 40 | 41 | //添加XML目录 42 | ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 43 | factory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); 44 | return factory.getObject(); 45 | } 46 | 47 | @Bean 48 | public MapperScannerConfigurer mapperScannerConfigurer() { 49 | MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); 50 | mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean"); 51 | mapperScannerConfigurer.setBasePackage(MAPPER_PACKAGE); 52 | 53 | //配置通用Mapper,详情请查阅官方文档 54 | Properties properties = new Properties(); 55 | properties.setProperty("mappers", MAPPER_INTERFACE_REFERENCE); 56 | properties.setProperty("notEmpty", "false");//insert、update是否判断字符串类型!='' 即 test="str != null"表达式内是否追加 and str != '' 57 | properties.setProperty("IDENTITY", "MYSQL"); 58 | mapperScannerConfigurer.setProperties(properties); 59 | 60 | return mapperScannerConfigurer; 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/configurer/WebMvcConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.company.project.configurer; 2 | 3 | 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.serializer.SerializerFeature; 6 | import com.alibaba.fastjson.support.config.FastJsonConfig; 7 | import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4; 8 | import com.company.project.core.result.Result; 9 | import com.company.project.core.result.ResultCode; 10 | import com.company.project.core.exception.ServiceException; 11 | import org.apache.commons.codec.digest.DigestUtils; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Value; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.http.converter.HttpMessageConverter; 18 | import org.springframework.web.method.HandlerMethod; 19 | import org.springframework.web.servlet.HandlerExceptionResolver; 20 | import org.springframework.web.servlet.ModelAndView; 21 | import org.springframework.web.servlet.NoHandlerFoundException; 22 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 23 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 24 | import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; 25 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 26 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 27 | 28 | import javax.servlet.ServletException; 29 | import javax.servlet.http.HttpServletRequest; 30 | import javax.servlet.http.HttpServletResponse; 31 | import java.io.IOException; 32 | import java.nio.charset.Charset; 33 | import java.util.ArrayList; 34 | import java.util.Collections; 35 | import java.util.List; 36 | 37 | /** 38 | * Spring MVC 配置 39 | */ 40 | @Configuration 41 | public class WebMvcConfigurer extends WebMvcConfigurerAdapter { 42 | 43 | private final Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class); 44 | @Value("${spring.profiles.active}") 45 | private String env;//当前激活的配置文件 46 | 47 | //使用阿里 FastJson 作为JSON MessageConverter 48 | @Override 49 | public void configureMessageConverters(List> converters) { 50 | FastJsonHttpMessageConverter4 converter = new FastJsonHttpMessageConverter4(); 51 | FastJsonConfig config = new FastJsonConfig(); 52 | config.setSerializerFeatures(SerializerFeature.WriteMapNullValue,//保留空的字段 53 | SerializerFeature.WriteNullStringAsEmpty,//String null -> "" 54 | SerializerFeature.WriteNullNumberAsZero);//Number null -> 0 55 | converter.setFastJsonConfig(config); 56 | converter.setDefaultCharset(Charset.forName("UTF-8")); 57 | converters.add(converter); 58 | } 59 | 60 | 61 | //统一异常处理 62 | @Override 63 | public void configureHandlerExceptionResolvers(List exceptionResolvers) { 64 | exceptionResolvers.add(new HandlerExceptionResolver() { 65 | public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) { 66 | Result result = new Result(); 67 | if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误” 68 | result.setCode(((ServiceException) e).getCode()); 69 | result.setMessage(e.getMessage()); 70 | logger.info(e.getMessage()); 71 | } else if (e instanceof NoHandlerFoundException) { 72 | result.setCode(ResultCode.NOT_FOUND); 73 | } else if (e instanceof ServletException) { 74 | result.setCode(ResultCode.FAIL).setMessage(e.getMessage()); 75 | } else { 76 | //系统内部异常,不返回给客户端,内部记录错误日志 77 | result.setCode(ResultCode.INTERNAL_SERVER_ERROR); 78 | String message; 79 | if (handler instanceof HandlerMethod) { 80 | HandlerMethod handlerMethod = (HandlerMethod) handler; 81 | message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s", 82 | request.getRequestURI(), 83 | handlerMethod.getBean().getClass().getName(), 84 | handlerMethod.getMethod().getName(), 85 | e.getMessage()); 86 | } else { 87 | message = e.getMessage(); 88 | } 89 | logger.error(message, e); 90 | } 91 | responseResult(response, result); 92 | return new ModelAndView(); 93 | } 94 | 95 | }); 96 | } 97 | 98 | 99 | //解决跨域问题 100 | @Override 101 | public void addCorsMappings(CorsRegistry registry) { 102 | registry.addMapping("/**") 103 | .allowedOrigins("*") 104 | .allowCredentials(true) 105 | .allowedMethods("GET", "POST", "DELETE", "PUT"); 106 | } 107 | 108 | //添加拦截器 109 | @Override 110 | public void addInterceptors(InterceptorRegistry registry) { 111 | //接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。 112 | if (!"dev".equals(env)) { //开发环境忽略签名认证 113 | registry.addInterceptor(new HandlerInterceptorAdapter() { 114 | @Override 115 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 116 | //验证签名 117 | boolean pass = validateSign(request); 118 | if (pass) { 119 | return true; 120 | } else { 121 | logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}", 122 | request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap())); 123 | 124 | Result result = new Result(); 125 | result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败"); 126 | responseResult(response, result); 127 | return false; 128 | } 129 | } 130 | }); 131 | } 132 | } 133 | 134 | 135 | private void responseResult(HttpServletResponse response, Result result) { 136 | response.setCharacterEncoding("UTF-8"); 137 | response.setHeader("Content-type", "application/json;charset=UTF-8"); 138 | response.setStatus(200); 139 | try { 140 | response.getWriter().write(JSON.toJSONString(result)); 141 | } catch (IOException ex) { 142 | logger.error(ex.getMessage()); 143 | } 144 | } 145 | 146 | /** 147 | * 一个简单的签名认证,规则: 148 | * 1. 将请求参数按ascii码排序 149 | * 2. 拼接为a=value&b=value...这样的字符串(不包含sign) 150 | * 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较 151 | */ 152 | private boolean validateSign(HttpServletRequest request) { 153 | String requestSign = request.getParameter("sign");//获得请求签名,如sign=19e907700db7ad91318424a97c54ed57 154 | if (StringUtils.isEmpty(requestSign)) { 155 | return false; 156 | } 157 | List keys = new ArrayList(request.getParameterMap().keySet()); 158 | keys.remove("sign");//排除sign参数 159 | Collections.sort(keys);//排序 160 | 161 | StringBuilder sb = new StringBuilder(); 162 | for (String key : keys) { 163 | sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串 164 | } 165 | String linkString = sb.toString(); 166 | linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最后一个'&' 167 | 168 | String secret = "Potato";//密钥,自己修改 169 | String sign = DigestUtils.md5Hex(linkString + secret);//混合密钥md5 170 | 171 | return StringUtils.equals(sign, requestSign);//比较 172 | } 173 | 174 | private String getIpAddress(HttpServletRequest request) { 175 | String ip = request.getHeader("x-forwarded-for"); 176 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 177 | ip = request.getHeader("Proxy-Client-IP"); 178 | } 179 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 180 | ip = request.getHeader("WL-Proxy-Client-IP"); 181 | } 182 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 183 | ip = request.getHeader("HTTP_CLIENT_IP"); 184 | } 185 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 186 | ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 187 | } 188 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 189 | ip = request.getRemoteAddr(); 190 | } 191 | // 如果是多级代理,那么取第一个ip为客户端ip 192 | if (ip != null && ip.indexOf(",") != -1) { 193 | ip = ip.substring(0, ip.indexOf(",")).trim(); 194 | } 195 | 196 | return ip; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/constant/ProjectConstant.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.constant; 2 | 3 | /** 4 | * 项目常量 5 | */ 6 | public final class ProjectConstant { 7 | public static final String BASE_PACKAGE = "com.company.project";//项目基础包名称,根据自己公司的项目修改 8 | 9 | public static final String MODEL_PACKAGE = BASE_PACKAGE + ".model";//Model所在包 10 | public static final String MAPPER_PACKAGE = BASE_PACKAGE + ".dao";//Mapper所在包 11 | public static final String SERVICE_PACKAGE = BASE_PACKAGE + ".service";//Service所在包 12 | public static final String SERVICE_IMPL_PACKAGE = SERVICE_PACKAGE + ".impl";//ServiceImpl所在包 13 | public static final String CONTROLLER_PACKAGE = BASE_PACKAGE + ".web";//Controller所在包 14 | 15 | public static final String MAPPER_INTERFACE_REFERENCE = BASE_PACKAGE + ".core.mapper.Mapper";//Mapper插件基础接口的完全限定名 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/exception/ServiceException.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.exception; 2 | 3 | import com.company.project.core.result.ResultCode; 4 | 5 | /** 6 | * 服务(业务)异常如“ 账号或密码错误 ”,该异常只做INFO级别的日志记录 @see WebMvcConfigurer 7 | */ 8 | public class ServiceException extends Exception { 9 | private int code; 10 | private String extraMessage; 11 | 12 | public ServiceException(ResultCode resultCode) { 13 | super(resultCode.getMessage()); 14 | this.code=resultCode.getCode(); 15 | } 16 | 17 | 18 | public ServiceException(String message, Throwable cause) { 19 | 20 | } 21 | public ServiceException(int code,String message,String extraMessage,Throwable cause){ 22 | super(message,cause); 23 | this.code=code; 24 | this.extraMessage=extraMessage; 25 | } 26 | 27 | 28 | 29 | public ServiceException(ResultCode resultCode, String extraMessage){ 30 | this(resultCode.getCode(),resultCode.getMessage(),extraMessage,null); 31 | } 32 | 33 | public ServiceException(String extraMessage){ 34 | this(ResultCode.INVALID_PARAM,extraMessage); 35 | } 36 | 37 | 38 | public int getCode() { 39 | return code; 40 | } 41 | 42 | public String getExtraMessage() { 43 | return extraMessage; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/mapper/Mapper.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.mapper; 2 | 3 | import tk.mybatis.mapper.common.BaseMapper; 4 | import tk.mybatis.mapper.common.ConditionMapper; 5 | import tk.mybatis.mapper.common.IdsMapper; 6 | import tk.mybatis.mapper.common.special.InsertListMapper; 7 | 8 | /** 9 | * 定制版MyBatis Mapper插件接口,如需其他接口参考官方文档自行添加。 10 | */ 11 | public interface Mapper 12 | extends 13 | BaseMapper, 14 | ConditionMapper, 15 | IdsMapper, 16 | InsertListMapper { 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/result/Result.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.result; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | /** 6 | * 统一API响应结果封装 7 | */ 8 | public class Result { 9 | private int code; 10 | private String message; 11 | private Object data; 12 | 13 | public Result(){ 14 | } 15 | 16 | public Result(ResultCode resultCode) { 17 | this.code = resultCode.getCode(); 18 | this.message=resultCode.getMessage(); 19 | } 20 | 21 | public Result(ResultCode resultCode,Object data) { 22 | this.code = resultCode.getCode(); 23 | this.message=resultCode.getMessage(); 24 | this.data=data; 25 | } 26 | 27 | 28 | 29 | 30 | public Result setCode(ResultCode resultCode) { 31 | this.code = resultCode.getCode(); 32 | this.message=resultCode.getMessage(); 33 | return this; 34 | } 35 | 36 | public int getCode() { 37 | return code; 38 | } 39 | 40 | public Result setCode(int code) { 41 | this.code = code; 42 | return this; 43 | } 44 | 45 | public String getMessage() { 46 | return message; 47 | } 48 | 49 | public Result setMessage(String message) { 50 | this.message = message; 51 | return this; 52 | } 53 | 54 | public Object getData() { 55 | return data; 56 | } 57 | 58 | public Result setData(Object data) { 59 | this.data = data; 60 | return this; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return JSON.toJSONString(this); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/result/ResultCode.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.result; 2 | 3 | /** 4 | * 响应码枚举,参考HTTP状态码的语义 5 | */ 6 | public enum ResultCode { 7 | SUCCESS(1, "SUCCESS"),//成功 8 | FAIL(400, "访问失败"),//失败 9 | UNAUTHORIZED(401, "签名错误"),//未认证(签名错误) 10 | NOT_FOUND(404, "此接口不存在"),//接口不存在 11 | INTERNAL_SERVER_ERROR(500, "系统繁忙,请稍后再试"),//服务器内部错误 12 | INVALID_PARAM(10000, "参数错误"), 13 | 14 | 15 | 16 | ; 17 | private int code; 18 | private String message; 19 | 20 | ResultCode(int code, String message) { 21 | this.code = code; 22 | this.message = message; 23 | } 24 | 25 | 26 | public int getCode() { 27 | return code; 28 | } 29 | 30 | public String getMessage() { 31 | return message; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/result/ResultGenerator.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.result; 2 | 3 | /** 4 | * 响应结果生成工具 5 | */ 6 | public class ResultGenerator { 7 | private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS"; 8 | 9 | public static Result genSuccessResult() { 10 | return new Result() 11 | .setCode(ResultCode.SUCCESS) 12 | .setMessage(DEFAULT_SUCCESS_MESSAGE); 13 | } 14 | 15 | public static Result genSuccessResult(Object data) { 16 | return new Result() 17 | .setCode(ResultCode.SUCCESS) 18 | .setMessage(DEFAULT_SUCCESS_MESSAGE) 19 | .setData(data); 20 | } 21 | 22 | public static Result genFailResult(String message) { 23 | return new Result() 24 | .setCode(ResultCode.FAIL) 25 | .setMessage(message); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/AbstractService.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service; 2 | 3 | 4 | import com.company.project.core.mapper.Mapper; 5 | import com.company.project.core.exception.ServiceException; 6 | import org.apache.ibatis.exceptions.TooManyResultsException; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import tk.mybatis.mapper.entity.Condition; 9 | 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.ParameterizedType; 12 | import java.util.List; 13 | 14 | /** 15 | * 基于通用MyBatis Mapper插件的Service接口的实现 16 | */ 17 | public abstract class AbstractService implements Service { 18 | 19 | @Autowired 20 | protected Mapper mapper; 21 | 22 | private Class modelClass; // 当前泛型真实类型的Class 23 | 24 | public AbstractService() { 25 | ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass(); 26 | modelClass = (Class) pt.getActualTypeArguments()[0]; 27 | } 28 | 29 | public void save(T model) { 30 | mapper.insertSelective(model); 31 | } 32 | 33 | public void save(List models) { 34 | mapper.insertList(models); 35 | } 36 | 37 | public void deleteById(Integer id) { 38 | mapper.deleteByPrimaryKey(id); 39 | } 40 | 41 | public void deleteByIds(String ids) { 42 | mapper.deleteByIds(ids); 43 | } 44 | 45 | public void update(T model) { 46 | mapper.updateByPrimaryKeySelective(model); 47 | } 48 | 49 | public T findById(Integer id) { 50 | return mapper.selectByPrimaryKey(id); 51 | } 52 | 53 | @Override 54 | public T findBy(String fieldName, Object value) throws TooManyResultsException,ServiceException { 55 | try { 56 | T model = modelClass.newInstance(); 57 | Field field = modelClass.getDeclaredField(fieldName); 58 | field.setAccessible(true); 59 | field.set(model, value); 60 | return mapper.selectOne(model); 61 | } catch (ReflectiveOperationException e) { 62 | throw new ServiceException(e.getMessage(), e); 63 | } 64 | } 65 | 66 | public List findByIds(String ids) { 67 | return mapper.selectByIds(ids); 68 | } 69 | 70 | public List findByCondition(Condition condition) { 71 | return mapper.selectByCondition(condition); 72 | } 73 | 74 | public List findAll() { 75 | return mapper.selectAll(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/AsyncService/AsyncConfig.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.AsyncService; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.scheduling.annotation.EnableAsync; 5 | 6 | @Configuration 7 | @EnableAsync 8 | public class AsyncConfig { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/AsyncService/AsyncNeedResponseService.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.AsyncService; 2 | 3 | 4 | import org.springframework.scheduling.annotation.Async; 5 | import org.springframework.scheduling.annotation.AsyncResult; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.concurrent.Future; 9 | 10 | /** 11 | * 异步请求应答模式 12 | * 对于,请求的内容,需要应答,例如我们需要在多个方法调用都完成后,才进行接下来的操作,此时我们可以利用 Java 的 Future-Listener 机制来实现异步服务调用。 13 | */ 14 | @Service 15 | public class AsyncNeedResponseService { 16 | @Async 17 | public Future sendA() throws Exception { 18 | System.out.println("send A"); 19 | Long startTime = System.currentTimeMillis(); 20 | Thread.sleep(2000); 21 | Long endTime = System.currentTimeMillis(); 22 | System.out.println("耗时:" + (endTime - startTime)); 23 | return new AsyncResult<>("success"); 24 | } 25 | 26 | @Async 27 | public Future sendB() throws Exception { 28 | System.out.println("send B"); 29 | Long startTime = System.currentTimeMillis(); 30 | Thread.sleep(2000); 31 | Long endTime = System.currentTimeMillis(); 32 | System.out.println("耗时:" + (endTime - startTime)); 33 | return new AsyncResult<>("success"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/AsyncService/AsyncNoResponseService.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.AsyncService; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import org.springframework.stereotype.Service; 5 | 6 | 7 | /** 8 | * 异步任务示例 9 | * 单发服务模式 10 | * 多个服务之间逻辑上不存在相互依赖关系,执行先后顺序没有严格的要求,逻辑上可以被并行执行。对于单发服务只有请求,没有应答,很容易设计成异步的。发起服务调用后。立即返回,不需要同步阻塞等待应答。 11 | */ 12 | @Service 13 | public class AsyncNoResponseService { 14 | 15 | @Async 16 | public void sendA() throws InterruptedException { 17 | System.out.println("send A============================"); 18 | long startTime=System.currentTimeMillis(); 19 | Thread.sleep(5000); 20 | long endTime=System.currentTimeMillis(); 21 | System.out.println("A 耗时:"+(endTime-startTime)); 22 | 23 | } 24 | 25 | @Async 26 | public void sendB() throws InterruptedException{ 27 | System.out.println("send B============================"); 28 | long startTime=System.currentTimeMillis(); 29 | Thread.sleep(2000); 30 | long endTIme=System.currentTimeMillis(); 31 | System.out.println("B 耗时:"+(endTIme-startTime)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/Service.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service; 2 | 3 | import com.company.project.core.exception.ServiceException; 4 | import org.apache.ibatis.exceptions.TooManyResultsException; 5 | import tk.mybatis.mapper.entity.Condition; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Service 层 基础接口,其他Service 接口 请继承该接口 11 | */ 12 | public interface Service { 13 | void save(T model);//持久化 14 | void save(List models);//批量持久化 15 | void deleteById(Integer id);//通过主鍵刪除 16 | void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4” 17 | void update(T model);//更新 18 | T findById(Integer id);//通过ID查找 19 | T findBy(String fieldName, Object value) throws TooManyResultsException,ServiceException; //通过Model中某个成员变量名称(非数据表中column的名称)查找,value需符合unique约束 20 | List findByIds(String ids);//通过多个ID查找//eg:ids -> “1,2,3,4” 21 | List findByCondition(Condition condition);//根据条件查找 22 | List findAll();//获取所有 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/amqp/RabbitMQConfig.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.amqp; 2 | 3 | import org.springframework.amqp.core.Queue; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | 8 | /** 9 | * 连接配置,使用哪个队列 10 | */ 11 | @Configuration 12 | public class RabbitMQConfig { 13 | public static final String QUEUE_NAME="spring-boot"; 14 | @Bean 15 | public Queue queue(){ 16 | return new Queue(QUEUE_NAME); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/amqp/Receiver.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.amqp; 2 | 3 | import org.springframework.amqp.core.AmqpTemplate; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Service; 6 | 7 | import javax.annotation.Resource; 8 | 9 | @Service 10 | public class Receiver { 11 | @Resource 12 | private AmqpTemplate amqpTemplate; 13 | @RabbitListener(queues = RabbitMQConfig.QUEUE_NAME) 14 | public void receive(String message){ 15 | System.out.println("Receive:"+message); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/amqp/Sender.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.amqp; 2 | 3 | import org.springframework.amqp.core.AmqpTemplate; 4 | import org.springframework.stereotype.Service; 5 | 6 | import javax.annotation.Resource; 7 | import java.util.Date; 8 | 9 | /** 10 | * 消息生产者例子 11 | */ 12 | @Service 13 | public class Sender { 14 | @Resource 15 | private AmqpTemplate amqpTemplate; 16 | public void send(){ 17 | System.out.println("发送消息中..."); 18 | Date date=new Date(); 19 | amqpTemplate.convertAndSend(RabbitMQConfig.QUEUE_NAME,"您好,现在的时间是"+date.toString()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/dubbo/City.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.dubbo; 2 | 3 | import com.company.project.model.BaseDO; 4 | 5 | 6 | 7 | /** 8 | * 城市实体类 9 | * 10 | * Created by bysocket on 07/02/2017. 11 | */ 12 | public class City extends BaseDO { 13 | 14 | private static final long serialVersionUID = -1L; 15 | 16 | /** 17 | * 城市编号 18 | */ 19 | private Long id; 20 | 21 | /** 22 | * 省份编号 23 | */ 24 | private Long provinceId; 25 | 26 | /** 27 | * 城市名称 28 | */ 29 | private String cityName; 30 | 31 | /** 32 | * 描述 33 | */ 34 | private String description; 35 | 36 | public City() { 37 | } 38 | 39 | public City(Long id, Long provinceId, String cityName, String description) { 40 | this.id = id; 41 | this.provinceId = provinceId; 42 | this.cityName = cityName; 43 | this.description = description; 44 | } 45 | 46 | public Long getId() { 47 | return id; 48 | } 49 | 50 | public void setId(Long id) { 51 | this.id = id; 52 | } 53 | 54 | public Long getProvinceId() { 55 | return provinceId; 56 | } 57 | 58 | public void setProvinceId(Long provinceId) { 59 | this.provinceId = provinceId; 60 | } 61 | 62 | public String getCityName() { 63 | return cityName; 64 | } 65 | 66 | public void setCityName(String cityName) { 67 | this.cityName = cityName; 68 | } 69 | 70 | public String getDescription() { 71 | return description; 72 | } 73 | 74 | public void setDescription(String description) { 75 | this.description = description; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/dubbo/CityDubboService.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.dubbo; 2 | 3 | /** 4 | * 城市业务 Dubbo 服务层 5 | * 6 | * Created by bysocket on 28/02/2017. 7 | */ 8 | public interface CityDubboService { 9 | 10 | /** 11 | * 根据城市名称,查询城市信息 12 | * @param cityName 13 | */ 14 | City findCityByName(String cityName); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/dubbo/impl/CityDubboServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.dubbo.impl; 2 | 3 | import com.alibaba.dubbo.config.annotation.Service; 4 | import com.company.project.core.service.dubbo.City; 5 | import com.company.project.core.service.dubbo.CityDubboService; 6 | 7 | 8 | /** 9 | * 城市业务 Dubbo 服务层实现层 10 | * 11 | * Created by bysocket on 28/02/2017. 12 | */ 13 | // 注册为 Dubbo 服务 14 | @Service(version = "1.0.0") 15 | public class CityDubboServiceImpl implements CityDubboService { 16 | 17 | public City findCityByName(String cityName) { 18 | return new City(1L,2L,"杭州","是我的故乡"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/dynProps4Files/DynProps4FilesService.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.dynProps4Files; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.apache.commons.lang.StringUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.stereotype.Component; 8 | 9 | 10 | import java.io.*; 11 | import java.util.*; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * 动态配置文件,可以设置更新周期 16 | * 配置读取读取服务 17 | */ 18 | @Component 19 | public class DynProps4FilesService { 20 | private Logger _log = LoggerFactory.getLogger(DynProps4FilesService.class); 21 | /** 22 | * 属性文件 23 | */ 24 | private File[] fileArray; 25 | /** 26 | * 启动延时 27 | */ 28 | private long delay; 29 | /** 30 | * 更新周期 31 | */ 32 | private long period; 33 | /** 34 | * 属性对象 35 | */ 36 | private Properties property = new Properties(); 37 | /** 38 | * 文件监控器 39 | */ 40 | private List monitors; 41 | 42 | /** 43 | * @param files 属性文件 44 | * @param delay 从DynProps被创建到第一次动态监视的时间间隔. 约束范围delay > 0 45 | * @param period 动态监视的时间间隔. 约束范围period >= 0;等于0表示不执行动态监视,退化为静态配置文件. 46 | */ 47 | public DynProps4FilesService(File[] files, long delay, long period) throws IOException { 48 | this.fileArray = files; 49 | this.delay = delay; 50 | this.period = period; 51 | init(); 52 | } 53 | 54 | public DynProps4FilesService(List fileNames, long delay, long period) throws IOException { 55 | this.delay = delay; 56 | this.period = period; 57 | fileArray = new File[fileNames.size()]; 58 | int index = 0; 59 | for (String oriFileName : fileNames) { 60 | String fileName = oriFileName.trim(); 61 | if (StringUtils.indexOfIgnoreCase(fileName, "classpath:") == 0) { 62 | fileArray[index++] = new File( 63 | this.getClass().getClassLoader().getResource("").getPath() + File.separator + 64 | fileName.substring("classpath:".length())); 65 | } else { 66 | fileArray[index++] = new File(fileName); 67 | } 68 | } 69 | 70 | init(); 71 | } 72 | 73 | 74 | public DynProps4FilesService(String fileNames, long delay, long period) throws IOException { 75 | this.delay = delay; 76 | this.period = period; 77 | boolean isClassPath = false; 78 | if (fileNames.startsWith("classpath")) { 79 | fileNames = fileNames.substring("classpath:".length()); 80 | isClassPath = true; 81 | } 82 | String[] fileName = fileNames.split("[,|,|;|;]"); 83 | fileArray = new File[fileName.length]; 84 | if (isClassPath) { 85 | for (int i = 0; i < fileName.length; i++) { 86 | fileArray[i] = new File(this.getClass().getClassLoader().getResource("").getPath() + fileName[i]); 87 | } 88 | } else { 89 | for (int i = 0; i < fileName.length; i++) { 90 | fileArray[i] = new File(fileName[i]); 91 | } 92 | } 93 | 94 | init(); 95 | } 96 | 97 | public DynProps4FilesService(File[] files, long period) throws IOException { 98 | this(files, 0, period); 99 | } 100 | 101 | public DynProps4FilesService(String fileNames, long period) throws IOException { 102 | this.period = period; 103 | this.delay = 0; 104 | String[] fileName = fileNames.split("[,|,|;|;]"); 105 | 106 | File[] files = new File[fileName.length]; 107 | for (int i = 0; i < fileName.length; i++) { 108 | files[i] = new File(fileName[i]); 109 | } 110 | init(); 111 | } 112 | 113 | public DynProps4FilesService() { 114 | } 115 | 116 | /** 117 | * 加载属性文件,启动监控 118 | * 119 | * @throws IOException 加载文件时出现IO异常 120 | */ 121 | protected void load() throws IOException { 122 | update(); 123 | if (monitors == null) { 124 | monitors = new ArrayList(fileArray.length); 125 | } else { 126 | for (FileMonitor monitor : monitors) { 127 | try { 128 | monitor.timer.cancel(); 129 | } catch (Exception e) { 130 | _log.warn(String.format("Timer for file [%s] cancelling failed.", monitor.file.getAbsolutePath())); 131 | } 132 | } 133 | } 134 | 135 | for (File file : fileArray) { 136 | long lastModify = file.lastModified(); 137 | FileMonitor monitor = new FileMonitor(file, lastModify); 138 | this.monitors.add(monitor); 139 | monitor.doTask(); 140 | } 141 | } 142 | 143 | /** 144 | * 如果文件有更新调用此方法载入 145 | * 146 | * @throws IOException 没有找到文件或读文件错误时抛出 147 | */ 148 | protected void update() throws IOException { 149 | for (File file : fileArray) { 150 | InputStream in = null; 151 | try { 152 | in = new FileInputStream(file); 153 | this.property.load(in); 154 | } catch (Exception e) { 155 | if (e instanceof IOException) { 156 | throw (IOException) e; 157 | } 158 | 159 | throw new IOException(e); 160 | } finally { 161 | IOUtils.closeQuietly(in); 162 | } 163 | } 164 | } 165 | 166 | /** 167 | * @param key 需要获取属性值的KEY 168 | * @param def 默认值 169 | * 170 | * @return 属性值 171 | */ 172 | public String getProperty(String key, String def) { 173 | String val = this.property.getProperty(key); 174 | return val == null ? def : val.trim(); 175 | } 176 | 177 | public String getProperty(String key) { 178 | String val = this.property.getProperty(key); 179 | return val == null ? null : val.trim(); 180 | } 181 | 182 | /** 183 | * 设置属性值 184 | * 185 | * @param key 186 | * @param value 187 | */ 188 | public void setProperty(String key, String value) { 189 | this.property.setProperty(key, value); 190 | } 191 | 192 | /** 193 | * @param key 需要获取属性值的KEY 194 | * @param def 默认值 195 | * 196 | * @return 属性值 197 | * 198 | * @throws NumberFormatException 如果属性值不是整数形式 199 | */ 200 | public int getInt(String key, int def) throws NumberFormatException { 201 | String val = this.getProperty(key); 202 | return val == null ? def : Integer.parseInt(val); 203 | } 204 | 205 | public int getInt(String key) throws NumberFormatException { 206 | return getInt(key, 0); 207 | } 208 | 209 | public float getFloat(String key, float def) throws NumberFormatException { 210 | String val = this.getProperty(key); 211 | return val == null ? def : Float.parseFloat(val); 212 | } 213 | 214 | public float getFloat(String key) throws NumberFormatException { 215 | return getFloat(key, 0.0f); 216 | } 217 | 218 | public double getDouble(String key, double def) { 219 | String val = this.getProperty(key); 220 | return val == null ? def : Double.parseDouble(val); 221 | } 222 | public double getDouble(String key) throws NumberFormatException { 223 | return getDouble(key,0.0); 224 | } 225 | 226 | 227 | 228 | public long getLong(String key, long def) { 229 | String val = this.getProperty(key); 230 | return val == null ? def : Long.parseLong(val); 231 | } 232 | 233 | public long getLong(String key) throws NumberFormatException { 234 | return getLong(key, 0L); 235 | } 236 | 237 | private void init() throws IOException { 238 | for (File file : fileArray) { 239 | if (!file.exists() || file.length() == 0) { 240 | throw new IllegalArgumentException("动态配置文件 " + file.getAbsolutePath() + " 不存在,或是空文件!"); 241 | } 242 | if (delay <= 0) { 243 | throw new IllegalArgumentException("定时器延时时间不能为负数!"); 244 | } 245 | if (period <= 0) { 246 | throw new IllegalArgumentException("定时器更新周期不能为负数!"); 247 | } 248 | this.property = new Properties(); 249 | this.load();// 初始构造时,执行第一次加载. 250 | } 251 | //当进程终止时,取消定时任务 252 | Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownHook())); 253 | } 254 | 255 | private class ShutdownHook implements Runnable { 256 | private DynProps4FilesService dynProps4FilesService; 257 | 258 | @Override 259 | public void run() { 260 | System.out.println("Monitors cancelling start ..."); 261 | if (monitors != null) { 262 | for (FileMonitor monitor : monitors) { 263 | try { 264 | monitor.timer.cancel(); 265 | } catch (Exception e) { 266 | _log.warn(String.format("Timer for file [%s] cancelling failed.", 267 | monitor.file.getAbsolutePath())); 268 | } 269 | } 270 | } 271 | } 272 | } 273 | 274 | /** 275 | * 描述:一个内部私有类,实时监控文件有没有更新,如果更新则自动载入 276 | */ 277 | private class FileMonitor { 278 | 279 | private long lastModifiedTime; 280 | private File file; 281 | /** 282 | * 定时器,以守护线程方式启动 283 | */ 284 | private Timer timer = new Timer(true); 285 | 286 | /** 287 | * @param lastMonitorTime 最后的更新时间 288 | */ 289 | private FileMonitor(File file, long lastMonitorTime) { 290 | this.file = file; 291 | this.lastModifiedTime = lastMonitorTime; 292 | } 293 | 294 | /** 295 | * 对文件进行实时监控,有更新则自动载入 296 | */ 297 | private void doTask() { 298 | if (delay < 0) { 299 | delay = 0L; 300 | } 301 | if (period <= 0) { 302 | return;// 如果更新周期非正数,则退化成静态配置文件. 303 | } 304 | timer.scheduleAtFixedRate(new TimerTask() { 305 | @Override 306 | public void run() { 307 | long t = file.lastModified(); 308 | // 文件被删除 309 | // 如果动态更新过程中,配置文件被强制删除了,本次不执行任何更新.或者对配置文件进行恢复 310 | if (t == 0) { 311 | try { 312 | if (file.createNewFile()) { 313 | OutputStream fos = new FileOutputStream(file); 314 | property.store(fos, "文件被删除,自动恢复."); 315 | fos.close(); 316 | } 317 | } catch (IOException ioe2) { 318 | // 这里基本上只有磁盘空间满才会发生,暂时不处理 319 | } 320 | return; 321 | } 322 | // 文件被更新 323 | if (t > lastModifiedTime) { 324 | lastModifiedTime = t; 325 | // 2秒后还在改变,则本次更新不做处理 326 | try { 327 | TimeUnit.SECONDS.sleep(2); 328 | } catch (InterruptedException e) { 329 | // do nothing 330 | } 331 | if (t != file.lastModified()) { 332 | _log.info("文件可能未更新完成,本次不更新!"); 333 | } else { 334 | try { 335 | property.clear(); 336 | update(); 337 | _log.info("UPDATED " + file.getAbsolutePath()); 338 | } catch (IOException ioe) { 339 | _log.error("UPDATING " + file.getAbsolutePath() + " failed", ioe); 340 | } 341 | } 342 | _log.debug("-----------------------:" + property.keySet()); 343 | } 344 | }// end run() 345 | }, delay, period); 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/redis/RedisResult.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.redis; 2 | 3 | import com.company.project.model.BaseDO; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * redis 中取得的结果 9 | * Created by liwei on 2017/2/7. 10 | */ 11 | public class RedisResult extends BaseDO { 12 | 13 | /** 14 | * redis中是否存在 15 | */ 16 | private boolean exist = false; 17 | 18 | /** 19 | * redis中取得的数据 20 | */ 21 | private T result; 22 | 23 | /** 24 | * redis中取得的List数据 25 | */ 26 | private List listResult; 27 | /** 28 | * redis中的key是否存在。true:表示redis中存在Key,但对应的值为空值标记 29 | */ 30 | private boolean keyExists = false; 31 | /** 32 | * redis中key 对应在对象值 33 | */ 34 | private T resultObj; 35 | 36 | 37 | public boolean isExist() { 38 | return exist; 39 | } 40 | 41 | public void setExist(boolean exist) { 42 | this.exist = exist; 43 | } 44 | 45 | public T getResult() { 46 | return result; 47 | } 48 | 49 | public void setResult(T result) { 50 | this.result = result; 51 | } 52 | 53 | public List getListResult() { 54 | return listResult; 55 | } 56 | 57 | public void setListResult(List listResult) { 58 | this.listResult = listResult; 59 | } 60 | 61 | public void setKeyExists(boolean keyExists) { 62 | this.keyExists = keyExists; 63 | } 64 | public boolean isKeyExists() { 65 | return keyExists; 66 | } 67 | 68 | public T getResultObj() { 69 | return resultObj; 70 | } 71 | 72 | public void setResultObj(T resultObj) { 73 | this.resultObj = resultObj; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/redis/RedisService.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.redis; 2 | 3 | import org.codehaus.jackson.map.ObjectMapper; 4 | 5 | import java.util.Set; 6 | 7 | /** 8 | * Redis 服务接口 9 | * Jimersy Lee 10 | * 2017-09-18 14:58:21 11 | */ 12 | public interface RedisService { 13 | /** 14 | * NX: 当且仅当缓存中特定的key不存在时设定数据 15 | */ 16 | String NXXX_SET_IF_NOT_EXISTS = "nx"; 17 | /** 18 | * XX: 当且仅当缓存中特定的key存在时设定数据 19 | */ 20 | String NXXX_SET_IF_EXISTS = "xx"; 21 | 22 | /** 23 | * EX:缓存失效的时间单位:秒 24 | */ 25 | String EXPX_SECONDS = "ex"; 26 | 27 | /** 28 | * EX:缓存失效的时间单位:毫秒 29 | */ 30 | String EXPX_MILLISECOND = "px"; 31 | 32 | /** 33 | * 默认过期时间 ,3600(秒) 34 | */ 35 | int DEFAULT_EXPIRE_TIME = 3600; 36 | 37 | ObjectMapper om = new ObjectMapper(); 38 | 39 | 40 | /** 41 | * 空白占位符 42 | */ 43 | String BLANK_CONTENT = "__BLANK__"; 44 | 45 | /** 46 | * 初始化操作 47 | */ 48 | void init(); 49 | 50 | 51 | void destroy(); 52 | 53 | // /** 54 | // * 从连接池里取连接(用完连接后必须销毁) 55 | // * 56 | // * @return 57 | // */ 58 | // Jedis getResource(); 59 | 60 | // /** 61 | // * 用完后,销毁连接(必须) 62 | // * 63 | // * @param jedis 64 | // */ 65 | // void destroyResource(Jedis jedis); 66 | 67 | /** 68 | * 根据key取数据 69 | * 70 | * @param key 71 | * @return 72 | */ 73 | String get(String key); 74 | 75 | 76 | /** 77 | * 根据key取对象数据(不支持Collection数据类型) 78 | * 79 | * @param key 80 | * @param clazz 81 | * @return 82 | */ 83 | T get(String key, Class clazz); 84 | 85 | 86 | /** 87 | * 根据key取对象数据(不支持Collection数据类型) 88 | * 89 | * @param key 90 | * @param clazz 91 | * @param 92 | * @return 93 | */ 94 | RedisResult getResult(String key, Class clazz); 95 | 96 | /** 97 | * 根据key取 Collection 对象数据 98 | * 99 | * @param key 100 | * @param elementClazz 集合元素类型 101 | * @param 102 | * @return 103 | */ 104 | RedisResult getListResult(String key, Class elementClazz); 105 | 106 | 107 | /** 108 | * 写入/修改 缓存内容 109 | * 110 | * @param key 111 | * @param obj 112 | * @return 113 | */ 114 | String set(String key, Object obj); 115 | 116 | 117 | /** 118 | * 写入/修改 缓存内容 119 | * 120 | * @param key 121 | * @param value 122 | * @return 123 | */ 124 | String set(String key, String value); 125 | 126 | 127 | /** 128 | * 写入/修改 缓存内容(无论key是否存在,均会更新key对应的值) 129 | * 130 | * @param key 131 | * @param obj 132 | * @param expireTime 缓存内容过期时间 (单位:秒) ,若expireTime小于0 则表示该内容不过期 133 | * @return 134 | */ 135 | String set(String key, Object obj, int expireTime); 136 | 137 | 138 | /** 139 | * 写入/修改 缓存内容(无论key是否存在,均会更新key对应的值) 140 | * 141 | * @param key 142 | * @param value 143 | * @param expireTime 缓存内容过期时间 (单位:秒) ,若expireTime小于0 则表示该内容不过期 144 | * @return 145 | */ 146 | String set(String key, String value, int expireTime); 147 | 148 | /** 149 | * 写入/修改 缓存内容 150 | * 151 | * @param key 152 | * @param value 153 | * @param nxxx 缓存写入值模式 详见 {@link RedisService#NXXX_SET_IF_EXISTS}, {@link RedisService#NXXX_SET_IF_NOT_EXISTS} 154 | * @param expx 缓存超时时间单位 详见{@link RedisService#EXPX_SECONDS}, {@link RedisService#EXPX_MILLISECOND} 155 | * @param expiredTime 缓存存活时长,必须 大于0 156 | * @return 157 | */ 158 | String set(String key, String value, String nxxx, String expx, long expiredTime); 159 | 160 | /** 161 | * 仅当redis中不含对应的key时,设定缓存内容 162 | * 163 | * @param key 164 | * @param value 165 | * @param expiredTime 缓存内容过期时间 (单位:秒) ,若expireTime小于0 则表示该内容不过期 166 | * @return 167 | */ 168 | String setnx(String key, String value, long expiredTime); 169 | 170 | /** 171 | * 仅当redis中含有对应的key时,修改缓存内容 172 | * 173 | * @param key 174 | * @param value 175 | * @param expiredTime 缓存内容过期时间 (单位:秒) ,若expireTime小于0 则表示该内容不过期 176 | * @return 177 | */ 178 | String setxx(String key, String value, long expiredTime); 179 | 180 | 181 | /** 182 | * 根据key删除缓存, 183 | * 184 | * @param keys 185 | * @return 186 | */ 187 | Long delete(String... keys); 188 | 189 | /** 190 | * 判断对应的key是否存在 191 | * 192 | * @param key 193 | * @return 194 | */ 195 | boolean exists(String key); 196 | 197 | 198 | /** 199 | * redis 加法运算 200 | * 201 | * @param key 202 | * @param value 203 | * @return 运算结果 204 | */ 205 | Long incrBy(String key, long value); 206 | 207 | /** 208 | * 设定redis 对应的key的剩余存活时间 209 | * 210 | * @param key 211 | * @param seconds 212 | */ 213 | void setTTL(String key, int seconds); 214 | 215 | /** 216 | * 根据通配符表达式查询key值的set,通配符仅支持* 217 | * 218 | * @param pattern 如 ke6*abc等 219 | * @return 220 | */ 221 | Set keys(String pattern); 222 | 223 | /** 224 | * 将对象转为json字符串。若对象为null,则返回 {@link RedisService#BLANK_CONTENT} 225 | * 226 | * @param object 227 | * @return 228 | */ 229 | String toJsonString(Object object); 230 | 231 | /** 232 | * json序列化对象。 233 | * 234 | * @param value 235 | * @return 返回序列化后的字符串。若value为null,则返回 {@link RedisService#BLANK_CONTENT} 236 | */ 237 | String makeSerializedString(Object value); 238 | 239 | 240 | } 241 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/redis/impl/RedisKeyHelper.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.redis.impl; 2 | 3 | import org.springframework.context.annotation.Lazy; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * Redis Key 辅助类 8 | */ 9 | @Component 10 | @Lazy(false) 11 | public class RedisKeyHelper { 12 | /** 13 | * 应用级别前缀 14 | */ 15 | private static String SYS_PREFIX = "PROJECT_"; 16 | /** 17 | * 用户信息前缀 18 | */ 19 | private static String UIC_PREFIX = SYS_PREFIX + "UIC_"; 20 | /** 21 | * 商品中心信息前缀 22 | */ 23 | private static String IC_PREFIX = SYS_PREFIX + "IC_"; 24 | /** 25 | * 交易中心信息前缀 26 | */ 27 | private static String TC_PREFIX = SYS_PREFIX + "TC_"; 28 | /** 29 | * 优惠中心信息前缀 30 | */ 31 | private static String PROM_PREFIX = SYS_PREFIX + "PROM_"; 32 | /** 33 | * 会员中心信息前缀 34 | */ 35 | private static String MMB_PREFIX = SYS_PREFIX + "MMB_"; 36 | /** 37 | * 分布式互斥锁前缀 38 | */ 39 | private static String LOCK_PREFIX = SYS_PREFIX + "LOCK_"; 40 | 41 | /** 42 | * 单次登录的默认有效时长(单位:秒) 43 | */ 44 | public static final int DEFAULT_LOGIN_TIMEOUT = 3600 * 24 * 7; 45 | 46 | /** 47 | * 签到记录的默认有效时长(单位:秒) 48 | */ 49 | public static final int MMB_SIGN_TIMEOUT = 3600 * 12; 50 | /** 51 | * redis中加息券信息的保存时间(单位:秒) 52 | */ 53 | public static final int PROM_IRC_TIMEOUT = 60 * 3; 54 | /** 55 | * redis中借款人信息默认有效效时间(单位:秒) 56 | */ 57 | public static final int BORROWER_EXPIRE_TIMEOUT = 60 * 3;//redis缓存失效时间 58 | 59 | 60 | //商品中心KEY配置==begin======================================================================================== 61 | /** 62 | * 商品(资产标)前缀 63 | */ 64 | public static final String IC_ITEM_PREFIX = IC_PREFIX + "ITEM_"; 65 | /** 66 | * 商品(资产标)列表前缀 67 | */ 68 | public static final String IC_ITEM_LIST_PREFIX = IC_ITEM_PREFIX + "LIST_"; 69 | /** 70 | * 商品已投金额或份数前缀 71 | */ 72 | public static final String IC_ITEM_INVESTED_AMOUNT_PREFIX = IC_PREFIX + "INTESTED_AMT_"; 73 | public static final String IC_ITEM_DEAL_CREDITOR_PREFIX = IC_PREFIX + "DEAL_CREDITOR_"; 74 | 75 | //商品中心KEY配置==end========================================================================================== 76 | //优惠中心KEY配置==begin======================================================================================== 77 | /** 78 | * 加息券信息前缀 79 | */ 80 | public static String PROM_IRC_PREFIX = PROM_PREFIX + "IRC_"; 81 | 82 | /** 83 | * 优惠配置信息前缀 84 | */ 85 | public static String PROM_CONFIG_PREFIX = PROM_PREFIX + "CONFIG_"; 86 | /** 87 | * 红包列表信息前缀 88 | */ 89 | public static final String PROM_COUPON_LIST_PREFIX = PROM_PREFIX + "COUPON_LIST_"; 90 | //优惠中心KEY配置==end========================================================================================== 91 | //交易中心KEY配置========================================================================================== 92 | public static final String TC_TRANS_ACCOUNT_PREFIX = TC_PREFIX + "ACC_"; 93 | 94 | public static final String TC_TRANS_CURRENT_DEAL_CONFIG_PREFIX = TC_PREFIX + "CURRENT_DEAL_CONFIG_"; 95 | 96 | public static final String TC_TRANS_EXPERIENCE_MONEY_CONFIG_PREFIX = TC_PREFIX + "EXPERIENCE_MONEY_CONFIG_"; 97 | 98 | public static final String TC_TRANS_CURRENT_DEAL_LOAD_PREFIX = TC_PREFIX + "CURRENT_DEAL_LOAD_"; 99 | 100 | public static final String TC_TRANS_CONST_DEAL_ORDER_LIST_PREFIX = TC_PREFIX + "CURRENT_CONST_DEAL_ORDER_LIST_"; 101 | 102 | /** 103 | * 流水号redis key前缀 104 | */ 105 | public static final String TC_TRANS_SEQ_PREFIX = TC_PREFIX + "SEQ_"; 106 | 107 | //交易中心KEY配置==end========================================================================================== 108 | //用户中心KEY配置========================================================================================== 109 | /** 110 | * redis中登录用户token的key前缀 111 | */ 112 | public static String LOGIN_TOKEN_KEY_PREFIX = UIC_PREFIX + "LOGIN_TOKEN_"; 113 | /** 114 | * redis中登录用户USERID的key前缀 115 | */ 116 | public static String LOGIN_UID_KEY_PREFIX = UIC_PREFIX + "LOGIN_UID_"; 117 | /** 118 | * 用户信息前缀(手机号) 119 | */ 120 | public static String UIC_MOBILE_PREFIX = UIC_PREFIX + "MOB_"; 121 | /** 122 | * 用户角色前缀 123 | */ 124 | public static String UIC_ROLE_PREFIX = UIC_PREFIX + "B_R_"; 125 | public static String UIC_ROLE_CANCEL_SUFFIX = UIC_PREFIX + "CNL_"; 126 | //用户中心KEY配置==end========================================================================================== 127 | 128 | /** 129 | * 构建分布式锁的key 130 | * 131 | * @param clazz 132 | * @param key 133 | * @return 134 | */ 135 | public String makeLockKey(Class clazz, String key) { 136 | return buildKeyString(LOCK_PREFIX, clazz.getSimpleName(), key); 137 | } 138 | 139 | 140 | /** 141 | * 构建分布式锁的key 142 | * 143 | * @param key 144 | * @return 145 | */ 146 | public String makeLockKey(String key) { 147 | return buildKeyString(LOCK_PREFIX, key); 148 | } 149 | 150 | /** 151 | * 构造商品信息 redis key 152 | * 153 | * @param itemId 154 | * @return 155 | */ 156 | public String makeIcItemKey(long itemId) { 157 | return buildKeyString(IC_ITEM_PREFIX, itemId); 158 | } 159 | 160 | /** 161 | * 构造商品信息列表 redis key 162 | * 163 | * @param pageSize 164 | * @param pageNo 165 | * @return 166 | */ 167 | public String makeIcItemListKey(int pageSize, int pageNo) { 168 | return buildKeyString(IC_ITEM_LIST_PREFIX, pageSize, pageNo); 169 | } 170 | 171 | /** 172 | * 构造资产标已投金额前缀 173 | * 174 | * @param itemId 175 | * @return 176 | */ 177 | public String makeInvestedAmountKey(long itemId) { 178 | return buildKeyString(IC_ITEM_INVESTED_AMOUNT_PREFIX, itemId); 179 | } 180 | 181 | /** 182 | * 构造交易中心流水号前缀 183 | * 184 | * @param flag 185 | * @return 186 | */ 187 | public String makeSeqKey(String flag) { 188 | return buildKeyString(TC_TRANS_SEQ_PREFIX, flag); 189 | } 190 | 191 | public static String buildKeyString(Object... objs) { 192 | if (objs == null || objs.length == 0) { 193 | return ""; 194 | } 195 | StringBuilder builder = new StringBuilder(); 196 | boolean isFirst = true; 197 | for (Object obj : objs) { 198 | if (isFirst) { 199 | isFirst = false; 200 | } else { 201 | builder.append("_"); 202 | } 203 | if (obj instanceof Class) { 204 | builder.append(((Class) obj).getName()); 205 | } else { 206 | builder.append(obj); 207 | } 208 | } 209 | return builder.toString(); 210 | } 211 | 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/redis/impl/RedisServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.redis.impl; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.company.project.core.service.redis.RedisResult; 5 | import com.company.project.core.service.redis.RedisService; 6 | 7 | import com.company.project.core.service.dynProps4Files.DynProps4FilesService; 8 | import org.apache.commons.io.IOUtils; 9 | import org.apache.commons.lang.StringUtils; 10 | import org.codehaus.jackson.type.JavaType; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.core.env.Environment; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.util.CollectionUtils; 16 | import redis.clients.jedis.Jedis; 17 | import redis.clients.jedis.JedisPool; 18 | import redis.clients.jedis.JedisPoolConfig; 19 | 20 | import javax.annotation.PostConstruct; 21 | import javax.annotation.PreDestroy; 22 | import javax.annotation.Resource; 23 | import java.io.IOException; 24 | import java.util.*; 25 | 26 | /** 27 | * Redis 服务接口实现类 28 | * 29 | * @author liwei 30 | * 16/10/30 下午5:28 31 | */ 32 | @Component("redisService") 33 | public class RedisServiceImpl implements RedisService { 34 | 35 | private static final Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class); 36 | 37 | 38 | private static JedisPool pool = null; 39 | 40 | @Resource 41 | private DynProps4FilesService dynProps4Files; 42 | 43 | @Resource 44 | private Environment env; 45 | 46 | 47 | /** 48 | * 初始化操作 49 | */ 50 | @Override 51 | @PostConstruct 52 | public void init() { 53 | if (pool != null) { 54 | return; 55 | } 56 | /*JedisPoolConfig config = new JedisPoolConfig(); 57 | config.setMaxIdle(dynProps4Files.getInt("REDIS_MAX_IDLE", JedisPoolConfig.DEFAULT_MAX_IDLE)); 58 | config.setMaxTotal(dynProps4Files.getInt("REDIS_MAX_TOTAL", JedisPoolConfig.DEFAULT_MAX_TOTAL)); 59 | config.setMaxWaitMillis(dynProps4Files.getLong("REDIS_MAX_WAIT", JedisPoolConfig.DEFAULT_MAX_WAIT_MILLIS)); 60 | config.setTestOnBorrow(true); 61 | pool = new JedisPool(config, dynProps4Files.getProperty("REDIS_HOST"), 62 | dynProps4Files.getInt("REDIS_PORT", 6379), dynProps4Files.getInt( 63 | "REDIS_MAX_WAIT", 1000), dynProps4Files.getProperty("REDIS_PASSWORD", null));*/ 64 | 65 | JedisPoolConfig config = new JedisPoolConfig(); 66 | config.setMaxIdle(dynProps4Files.getInt("REDIS_MAX_IDLE", JedisPoolConfig.DEFAULT_MAX_IDLE)); 67 | config.setMaxTotal(dynProps4Files.getInt("REDIS_MAX_TOTAL", JedisPoolConfig.DEFAULT_MAX_TOTAL)); 68 | config.setMaxWaitMillis(dynProps4Files.getLong("REDIS_MAX_WAIT", JedisPoolConfig.DEFAULT_MAX_WAIT_MILLIS)); 69 | config.setTestOnBorrow(true); 70 | pool = new JedisPool(config, env.getProperty("spring.redis.host"), 71 | dynProps4Files.getInt("REDIS_PORT", 6379), dynProps4Files.getInt( 72 | "REDIS_MAX_WAIT", 1000), dynProps4Files.getProperty("REDIS_PASSWORD", null)); 73 | 74 | 75 | } 76 | 77 | @Override 78 | @PreDestroy 79 | public void destroy() { 80 | try { 81 | if (pool != null) { 82 | pool.destroy(); 83 | } 84 | 85 | } catch (Exception e) { 86 | //do nothing 87 | } 88 | } 89 | 90 | /** 91 | * 从连接池里取连接(用完连接后必须销毁) 92 | * 93 | * @return 94 | */ 95 | private Jedis getResource() { 96 | return pool.getResource(); 97 | 98 | } 99 | 100 | /** 101 | * 用完后,销毁连接(必须) 102 | * 103 | * @param jedis 104 | */ 105 | private void destroyResource(Jedis jedis) { 106 | if (jedis == null) { 107 | return; 108 | } 109 | IOUtils.closeQuietly(jedis); 110 | } 111 | 112 | /** 113 | * 根据key取数据 114 | * 115 | * @param key 116 | * @return 117 | */ 118 | public String get(String key) { 119 | 120 | if (StringUtils.isBlank(key)) { 121 | logger.warn("Params key is blank!"); 122 | return StringUtils.EMPTY; 123 | } 124 | Jedis jedis = this.getResource(); 125 | try { 126 | return jedis.get(key); 127 | } finally { 128 | this.destroyResource(jedis); 129 | } 130 | 131 | } 132 | 133 | /** 134 | * 根据key取对象数据(不支持Collection数据类型) 135 | * 136 | * @param key 137 | * @param clazz 138 | * @return 139 | */ 140 | @Override 141 | public T get(String key, Class clazz) { 142 | if (StringUtils.isBlank(key)) { 143 | logger.warn("Params key is blank!"); 144 | return null; 145 | } 146 | if (clazz == null) { 147 | logger.warn("Params clazz is null!"); 148 | return null; 149 | } 150 | String value = get(key); 151 | if (StringUtils.isBlank(value) || StringUtils.equalsIgnoreCase(value, BLANK_CONTENT)) { 152 | return null; 153 | } 154 | T obj = null; 155 | try { 156 | obj = om.readValue(value, clazz); 157 | } catch (IOException e) { 158 | logger.error("Can not unserialize obj to [{}] with string [{}]", clazz.getName(), value); 159 | } 160 | return obj; 161 | } 162 | 163 | /** 164 | * 写入/修改 缓存内容(无论key是否存在,均会更新key对应的值) 165 | * 166 | * @param key 167 | * @param obj 168 | * @param expireTime 缓存 内容过期时间 (单位:秒) ,若expireTime小于0 则表示该内容不过期 169 | * @return 170 | */ 171 | public String set(String key, Object obj, int expireTime) { 172 | String value = RedisService.BLANK_CONTENT; 173 | if (obj != null) { 174 | try { 175 | value = RedisService.om.writeValueAsString(obj); 176 | } catch (IOException e) { 177 | logger.error("Can not write object to redis:" + obj.toString(), e); 178 | } 179 | } 180 | return set(key, value, expireTime); 181 | } 182 | 183 | /** 184 | * 写入/修改 缓存内容(无论key是否存在,均会更新key对应的值) 185 | * 186 | * @param key 187 | * @param value 188 | * @param expireTime 缓存 内容过期时间 (单位:秒) ,若expireTime小于0 则表示该内容不过期 189 | * @return 190 | */ 191 | public String set(String key, String value, int expireTime) { 192 | if (StringUtils.isBlank(key)) { 193 | logger.warn("Params key is blank!"); 194 | return null; 195 | } 196 | 197 | if (value == null) { 198 | 199 | logger.warn("Params value is null!"); 200 | return null; 201 | } 202 | 203 | Jedis jedis = this.getResource(); 204 | try { 205 | String result = jedis.set(key, value); 206 | if (expireTime > 0) { 207 | jedis.expire(key, expireTime); 208 | } 209 | return result; 210 | } finally { 211 | this.destroyResource(jedis); 212 | } 213 | } 214 | 215 | /** 216 | * 根据key取对象数据(不支持Collection数据类型) 217 | * 218 | * @param key 219 | * @param clazz 220 | * @return 221 | */ 222 | @Override 223 | public RedisResult getResult(String key, Class clazz) { 224 | if (StringUtils.isBlank(key)) { 225 | logger.warn("Params key is blank!"); 226 | return null; 227 | } 228 | if (clazz == null) { 229 | logger.warn("Params clazz is null!"); 230 | return null; 231 | } 232 | RedisResult redisResult = new RedisResult(); 233 | 234 | String value = get(key); 235 | if (StringUtils.isBlank(value)) { 236 | redisResult.setExist(false); 237 | return redisResult; 238 | } 239 | //到此步,则表明redis中存在key 240 | redisResult.setExist(true); 241 | if (StringUtils.equalsIgnoreCase(value, BLANK_CONTENT)) { 242 | return redisResult; 243 | } 244 | T obj = null; 245 | try { 246 | obj = om.readValue(value, clazz); 247 | redisResult.setResult(obj); 248 | } catch (IOException e) { 249 | logger.error("Can not unserialize obj to [{}] with string [{}]", clazz.getName(), value); 250 | //到此步直接视为无值 251 | redisResult.setExist(false); 252 | } 253 | return redisResult; 254 | } 255 | 256 | /** 257 | * 根据key取 Collection 对象数据 258 | * 259 | * @param key 260 | * @param elementClazz 集合元素类型 261 | * @return 262 | */ 263 | @Override 264 | public RedisResult getListResult(String key, Class elementClazz) { 265 | if (StringUtils.isBlank(key)) { 266 | logger.warn("Params key is blank!"); 267 | return null; 268 | } 269 | 270 | if (elementClazz == null) { 271 | logger.warn("Params elementClazz is null!"); 272 | return null; 273 | } 274 | RedisResult redisResult = new RedisResult(); 275 | 276 | String value = get(key); 277 | if (StringUtils.isBlank(value)) { 278 | redisResult.setExist(false); 279 | return redisResult; 280 | } 281 | 282 | //到此步,则表明redis中存在key 283 | redisResult.setExist(true); 284 | if (StringUtils.equalsIgnoreCase(value, BLANK_CONTENT)) { 285 | return redisResult; 286 | } 287 | 288 | List list = null; 289 | try { 290 | list = om.readValue(value, getCollectionType(List.class, elementClazz)); 291 | redisResult.setListResult(list); 292 | } catch (IOException e) { 293 | logger.error("Can not unserialize list to [{}] with string [{}]", elementClazz.getName(), value); 294 | //到此步直接视为无值 295 | redisResult.setExist(false); 296 | } 297 | 298 | return redisResult; 299 | } 300 | 301 | /** 302 | * 写入/修改 缓存内容 303 | * 304 | * @param key 305 | * @param obj 306 | * @return 307 | */ 308 | @Override 309 | public String set(String key, Object obj) { 310 | String value = RedisService.BLANK_CONTENT; 311 | if (obj != null) { 312 | try { 313 | value = RedisService.om.writeValueAsString(obj); 314 | } catch (IOException e) { 315 | logger.error("Can not write object to redis:" + obj.toString(), e); 316 | } 317 | } 318 | return set(key, value); 319 | } 320 | 321 | private static JavaType getCollectionType(Class collectionClazz, 322 | Class elementClazz) { 323 | return om.getTypeFactory().constructCollectionType(collectionClazz, elementClazz); 324 | } 325 | 326 | 327 | /** 328 | * 写入/修改 缓存内容(默认有过期时间 1小时) 329 | * 330 | * @param key 331 | * @param value 332 | * @return 333 | */ 334 | @Override 335 | public String set(String key, String value) { 336 | return this.set(key, value, DEFAULT_EXPIRE_TIME); 337 | } 338 | 339 | 340 | /** 341 | * 写入/修改 缓存内容 342 | * 343 | * @param key 344 | * @param value 345 | * @param nxxx 缓存写入值模式 详见 {@link RedisService#NXXX_SET_IF_EXISTS}, {@link RedisService#NXXX_SET_IF_NOT_EXISTS} 346 | * @param expx 缓存超时时间单位 详见{@link RedisService#EXPX_SECONDS}, {@link RedisService#EXPX_MILLISECOND} 347 | * @param expiredTime 缓存存活时长,必须 大于0 348 | * @return 349 | */ 350 | @Override 351 | public String set(String key, String value, String nxxx, String expx, long expiredTime) { 352 | if (StringUtils.isBlank(key)) { 353 | logger.warn("Params key is blank!"); 354 | return null; 355 | } 356 | 357 | if (value == null) { 358 | logger.warn("Params value is null!"); 359 | return null; 360 | } 361 | 362 | Jedis jedis = this.getResource(); 363 | try { 364 | return jedis.set(key, value, nxxx, expx, expiredTime); 365 | } finally { 366 | this.destroyResource(jedis); 367 | } 368 | 369 | } 370 | 371 | /** 372 | * 仅当redis中不含对应的key时,设定缓存内容 373 | * 374 | * @param key 375 | * @param value 376 | * @param expiredTime 缓存内容过期时间 (单位:秒) ,expireTime必须大于0 377 | * @return 378 | */ 379 | @Override 380 | public String setnx(String key, String value, long expiredTime) { 381 | return this.set(key, value, NXXX_SET_IF_NOT_EXISTS, EXPX_SECONDS, expiredTime); 382 | } 383 | 384 | /** 385 | * 仅当redis中含有对应的key时,修改缓存内容 386 | * 387 | * @param key 388 | * @param value 389 | * @param expiredTime 缓存内容过期时间 (单位:秒) ,expireTime必须大于0 390 | * @return 391 | */ 392 | @Override 393 | public String setxx(String key, String value, long expiredTime) { 394 | return this.set(key, value, NXXX_SET_IF_EXISTS, EXPX_SECONDS, expiredTime); 395 | } 396 | 397 | /** 398 | * 根据key删除缓存 399 | * 400 | * @param keys 401 | * @return 402 | */ 403 | public Long delete(String... keys) { 404 | if (keys == null || keys.length == 0) { 405 | logger.warn("Params keys is null or 0 length!"); 406 | return -1L; 407 | } 408 | Jedis jedis = this.getResource(); 409 | try { 410 | return jedis.del(keys); 411 | } finally { 412 | this.destroyResource(jedis); 413 | } 414 | } 415 | 416 | /** 417 | * 判断对应的key是否存在 418 | * 419 | * @param key 420 | * @return 421 | */ 422 | public boolean exists(String key) { 423 | if (StringUtils.isBlank(key)) { 424 | //不接受空值 425 | return false; 426 | } 427 | Jedis jedis = this.getResource(); 428 | try { 429 | return jedis.exists(key); 430 | } finally { 431 | this.destroyResource(jedis); 432 | } 433 | 434 | 435 | } 436 | 437 | /** 438 | * redis 加法运算 439 | * 440 | * @param key 441 | * @param value 442 | * @return 443 | */ 444 | @Override 445 | public Long incrBy(String key, long value) { 446 | if (StringUtils.isBlank(key)) { 447 | logger.warn("Params key is blank!"); 448 | return null; 449 | } 450 | Jedis jedis = this.getResource(); 451 | try { 452 | return jedis.incrBy(key, value); 453 | } finally { 454 | this.destroyResource(jedis); 455 | } 456 | } 457 | 458 | /** 459 | * 设定redis 对应的key的剩余存活时间 460 | * 461 | * @param key 462 | * @param seconds 463 | */ 464 | @Override 465 | public void setTTL(String key, int seconds) { 466 | if (seconds < 0) { 467 | return; 468 | } 469 | if (StringUtils.isBlank(key)) { 470 | logger.warn("Params key is blank!"); 471 | return; 472 | } 473 | Jedis jedis = this.getResource(); 474 | try { 475 | jedis.expire(key, seconds); 476 | } finally { 477 | this.destroyResource(jedis); 478 | } 479 | } 480 | 481 | /** 482 | * 根据通配符表达式查询key值的set,通配符仅支持* 483 | * 484 | * @param pattern 如 ke6*abc等 485 | * @return 486 | */ 487 | public Set keys(String pattern) { 488 | 489 | if (StringUtils.isBlank(pattern)) { 490 | logger.warn("Params pattern is blank!"); 491 | return Collections.emptySet(); 492 | } 493 | Jedis jedis = this.getResource(); 494 | try { 495 | return jedis.keys(pattern); 496 | } finally { 497 | this.destroyResource(jedis); 498 | } 499 | } 500 | 501 | 502 | /** 503 | * 将对象转为json字符串。若对象为null,则返回 {@link RedisService#BLANK_CONTENT} 504 | * 505 | * @param object 506 | * @return 507 | */ 508 | @Override 509 | public String toJsonString(Object object) { 510 | if (object == null) { 511 | return BLANK_CONTENT; 512 | } 513 | 514 | if ((object instanceof Collection) && CollectionUtils.isEmpty((Collection) object)) { 515 | return BLANK_CONTENT; 516 | } 517 | 518 | if ((object instanceof Map) && CollectionUtils.isEmpty((Map) object)) { 519 | return BLANK_CONTENT; 520 | } 521 | 522 | try { 523 | return om.writeValueAsString(object); 524 | } catch (IOException e) { 525 | return null; 526 | } 527 | } 528 | 529 | @Override 530 | public String makeSerializedString(Object value) { 531 | if (value == null) { 532 | return BLANK_CONTENT; 533 | } 534 | 535 | if ((value instanceof Collection) && ((Collection) value).size() == 0) { 536 | return BLANK_CONTENT; 537 | } 538 | 539 | if ((value instanceof Map) && ((Map) value).size() == 0) { 540 | return BLANK_CONTENT; 541 | } 542 | 543 | 544 | return JSON.toJSONString(value); 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/zookeeper/MachineNode.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.zookeeper; 2 | 3 | /** 4 | * 机器节点 5 | * @author jimersylee 6 | * 7 | */ 8 | public class MachineNode { 9 | private static final String SPLITTER = ":"; 10 | 11 | /** 12 | * 机器名 13 | */ 14 | private String hostname; 15 | 16 | /** 17 | * 当前服务的端口号 18 | */ 19 | private int port; 20 | 21 | 22 | public String getHostname() { 23 | return hostname; 24 | } 25 | 26 | public void setHostname(String hostname) { 27 | this.hostname = hostname; 28 | } 29 | 30 | public int getPort() { 31 | return port; 32 | } 33 | 34 | public void setPort(int port) { 35 | this.port = port; 36 | } 37 | 38 | /** 39 | * 获取节点名称 40 | * 41 | * @return 42 | */ 43 | public String nodeName() { 44 | return hostname + SPLITTER + port; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/zookeeper/ZkManager.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.zookeeper; 2 | 3 | import com.company.project.core.service.zookeeper.watcher.DomiChildrenNodeHandler; 4 | import com.company.project.core.service.zookeeper.watcher.DomiNodeHandler; 5 | import org.apache.zookeeper.KeeperException; 6 | import org.apache.zookeeper.WatchedEvent; 7 | import org.apache.zookeeper.data.Stat; 8 | import org.codehaus.jackson.map.ObjectMapper; 9 | 10 | import java.io.IOException; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | 15 | /** 16 | * ZK 连接管理接口 17 | * 18 | * @author qingren.lw 19 | * 16-10-27 下午5:19 20 | */ 21 | public interface ZkManager { 22 | ObjectMapper simpleObjectMapper = new ObjectMapper(); 23 | /** 24 | * 多米交易中心 根节点根路径 25 | */ 26 | String TC_ROOT_PATH = "/domi_tc"; 27 | 28 | /** 29 | * 多米交易中心 机器节点根路径 30 | */ 31 | String TC_MACHINE_LIST_PATH = TC_ROOT_PATH + "/machine_list"; 32 | 33 | /** 34 | * zk路径分隔符 / 35 | */ 36 | String PATH_SPLITTER="/"; 37 | 38 | /** 39 | * 连接ZK 发布本机节点 40 | */ 41 | void init() throws IOException; 42 | 43 | 44 | /** 45 | * 等待建立连接 46 | */ 47 | void waitForConnected(); 48 | 49 | void countDownWaitForConnectedLatch(); 50 | 51 | /** 52 | * 追加监视的ZK事件 53 | * 54 | * @param event 55 | */ 56 | void addEvent(WatchedEvent event); 57 | 58 | 59 | /** 60 | * 为特定路径的节点 添加监听器(同时监听数据变化以及该节点存在性变化) 61 | * 62 | * @param path 63 | * @param handler 64 | */ 65 | void addNodeDataChangedHandler(String path, 66 | DomiNodeHandler handler) throws KeeperException, InterruptedException; 67 | 68 | /** 69 | * 为特定路径的节点 添加监听器(监听数子节点数量变化) 70 | * 71 | * @param path 72 | * @param handler 73 | */ 74 | void addChildrenChangedHandler(String path, 75 | DomiChildrenNodeHandler handler) throws KeeperException, InterruptedException; 76 | 77 | 78 | /** 79 | * 移除特定路径节点的监听器 80 | * 81 | * @param path 82 | */ 83 | void removeNodeDataChangedHandler(String path); 84 | 85 | /** 86 | * 移除特定路径节点的子节点处理器 87 | * 88 | * @param path 89 | */ 90 | void removeChildrenChangedHandler(String path); 91 | 92 | /** 93 | * 移除特定路径节点的所有处理器 94 | * 95 | * @param path 96 | */ 97 | void removeHandlerByPath(String path); 98 | 99 | /** 100 | * 判断是否有 path 节点的子节点监听处理器 101 | * 102 | * @param path 103 | */ 104 | boolean containChildrenChangedHandler(String path); 105 | 106 | /** 107 | * 判断是否有 path 节点的监听处理器 108 | * 109 | * @param path 110 | * @return 111 | */ 112 | boolean containNodeDataChangedHandler(String path); 113 | 114 | /** 115 | * 重新注册所有节点监听器 116 | */ 117 | void refreshAllNodeDataChangedHandlers(); 118 | 119 | 120 | /** 121 | * 重新注册所有子节点监听器 122 | */ 123 | void refreshAllChildrenChangedHandlers(); 124 | 125 | /** 126 | * 发布非持久化节点 127 | * 128 | * @param path 129 | * @param value 130 | * @return 实际创建的节点路径 131 | * @throws KeeperException 132 | * @throws InterruptedException 133 | */ 134 | String createEphemeralNode(String path, String value) throws KeeperException, InterruptedException; 135 | 136 | /** 137 | * 发布持久化节点 138 | * 139 | * @param path 140 | * @param value 141 | * @return 实际创建的节点路径 142 | * @throws KeeperException 143 | * @throws InterruptedException 144 | */ 145 | String createNode(String path, String value) throws KeeperException, InterruptedException; 146 | 147 | /** 148 | * 删除节点 149 | * 150 | * @param path 151 | * @throws KeeperException 152 | * @throws InterruptedException 153 | */ 154 | void deleteNode(String path) throws KeeperException, InterruptedException; 155 | 156 | 157 | /** 158 | * 递归删除节点 159 | * 160 | * @param path 161 | */ 162 | void deleteNodeRecursively(String path) throws KeeperException, InterruptedException; 163 | 164 | /** 165 | * 判断节点是否存在 166 | * 167 | * @param path 168 | * @return 169 | * @throws KeeperException 170 | * @throws InterruptedException 171 | */ 172 | boolean exists(String path) throws KeeperException, InterruptedException; 173 | 174 | /** 175 | * 判断节点是否存在 176 | * 177 | * @param path 178 | * @param watch 监听此节点create 、delete事件 179 | * @return 180 | * @throws KeeperException 181 | * @throws InterruptedException 182 | */ 183 | boolean exists(String path, boolean watch) throws KeeperException, InterruptedException; 184 | 185 | 186 | /** 187 | * 创建节点。若父节点不存在则先创建父节点 188 | * 189 | * @param path 190 | * @param value 191 | * @return 192 | * @throws KeeperException 193 | * @throws InterruptedException 194 | */ 195 | String createNodeRecursively(String path, String value) throws KeeperException, InterruptedException; 196 | 197 | /** 198 | * 创建节点信息,若该节点已存在则更新此节点数据 199 | * 200 | * @param path 201 | * @param value 202 | */ 203 | void createOrUpdateNodeData(String path, String value) throws KeeperException, InterruptedException; 204 | 205 | /** 206 | * 更新node数据 207 | * 208 | * @param path 209 | * @param value 210 | * @return 211 | * @throws KeeperException 212 | * @throws InterruptedException 213 | */ 214 | Stat updateNodeData(String path, String value) throws KeeperException, InterruptedException; 215 | 216 | 217 | /** 218 | * 更新node数据 219 | * 220 | * @param path 221 | * @param value 222 | * @param version 223 | * @return 224 | * @throws KeeperException 225 | * @throws InterruptedException 226 | */ 227 | Stat updateNodeData(String path, String value, int version) throws KeeperException, InterruptedException; 228 | 229 | 230 | /** 231 | * 获取节点数据 232 | * 233 | * @param path 234 | * @return 235 | */ 236 | String getData(String path) throws KeeperException, InterruptedException; 237 | 238 | 239 | /** 240 | * 获取节点数据 241 | * 242 | * @param path 243 | * @param stat 244 | * @return 245 | * @throws InterruptedException 246 | * @throws org.apache.zookeeper.KeeperException 247 | */ 248 | String getData(String path, Stat stat) throws KeeperException, InterruptedException; 249 | 250 | /** 251 | * 获取节点数据 252 | * 253 | * @param path 254 | * @param watch true:监听此节点的数据变化 255 | * @return 256 | */ 257 | String getData(String path, boolean watch) throws KeeperException, InterruptedException; 258 | 259 | /** 260 | * 获取节点数据 261 | * 262 | * @param path 263 | * @param watch true:监听此节点的数据变化 264 | * @param stat 265 | * @return 266 | * @throws KeeperException 267 | * @throws InterruptedException 268 | */ 269 | String getData(String path, boolean watch, Stat stat) throws KeeperException, InterruptedException; 270 | 271 | /** 272 | * 获取path节点的子节点名称列表 273 | * 274 | * @param path 275 | * @return 276 | */ 277 | List getChildren(String path) throws KeeperException, InterruptedException; 278 | 279 | /** 280 | * 获取path节点的子节点名称列表 281 | * 282 | * @param path 283 | * @param watch 监听节点个数变化的事件 284 | * @return 285 | */ 286 | List getChildren(String path, boolean watch) throws KeeperException, InterruptedException; 287 | 288 | 289 | /** 290 | * 获取本机节点对象 291 | * 292 | * @return 293 | */ 294 | MachineNode getLocalMachine(); 295 | 296 | /** 297 | * 从ZK端获取本机节点对象 298 | * 299 | * @return 300 | */ 301 | MachineNode getLocalMachineFromZk(); 302 | 303 | 304 | /** 305 | * 追加节点监听器(初始化前调用有效) 306 | * 307 | * @param nodeHandlerMap 308 | */ 309 | void setNodeDataChangedHandlerBeforeInit(Map nodeHandlerMap); 310 | 311 | /** 312 | * 追加子节点监听器(初始化前调用有效) 313 | * 314 | * @param childrenNodeHandlerMap 315 | */ 316 | void setChildrenChangedHandlerBeforeInit( 317 | Map childrenNodeHandlerMap); 318 | 319 | /** 320 | * 下线本机节点 321 | */ 322 | void offlineLocalMachineNode() throws KeeperException, InterruptedException; 323 | 324 | /** 325 | * 上线本机节点 326 | */ 327 | void onlineLocalMachineNode() throws KeeperException, InterruptedException, IOException; 328 | 329 | /** 330 | * 判断本机节点是否已在zk上注册 331 | * 332 | * @return 333 | */ 334 | boolean isLocalMachineOnline(); 335 | 336 | 337 | } 338 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/zookeeper/impl/DomiZkManager.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.zookeeper.impl; 2 | 3 | import com.company.project.core.service.zookeeper.MachineNode; 4 | import com.company.project.core.service.zookeeper.ZkManager; 5 | import com.company.project.core.service.zookeeper.watcher.DomiChildrenNodeHandler; 6 | import com.company.project.core.service.zookeeper.watcher.DomiNodeHandler; 7 | import com.company.project.core.service.zookeeper.watcher.DomiZkEventWatcher; 8 | import com.company.project.core.service.dynProps4Files.DynProps4FilesService; 9 | import org.apache.commons.lang.StringUtils; 10 | import org.apache.zookeeper.*; 11 | import org.apache.zookeeper.data.Stat; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.core.env.Environment; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.util.CollectionUtils; 17 | 18 | import javax.annotation.PostConstruct; 19 | import javax.annotation.Resource; 20 | import java.io.IOException; 21 | import java.io.UnsupportedEncodingException; 22 | import java.net.Inet4Address; 23 | import java.net.InetAddress; 24 | import java.net.NetworkInterface; 25 | import java.net.SocketException; 26 | import java.util.ArrayList; 27 | import java.util.Enumeration; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.concurrent.ConcurrentHashMap; 31 | import java.util.concurrent.CountDownLatch; 32 | import java.util.concurrent.LinkedBlockingQueue; 33 | import java.util.concurrent.TimeUnit; 34 | import java.util.concurrent.atomic.AtomicBoolean; 35 | import java.util.concurrent.locks.Lock; 36 | import java.util.concurrent.locks.ReentrantLock; 37 | 38 | /** 39 | * ZK 连接管理器

40 | *

41 | * 节点结构信息 42 | *

 43 |  * /domi_tc
 44 |  * |
 45 |  * +-machine_list
 46 |  *     |
 47 |  *     |-machine_name1: machine_info1
 48 |  *     +-machine_name2: machine_info2
 49 |  * 
50 | * 51 | * @author jimersylee 52 | * 2017-09-25 14:34:54 53 | */ 54 | @Service 55 | public class DomiZkManager implements ZkManager { 56 | private static final String DEFAULT_CHARSET = "UTF-8"; 57 | private static final Logger logger = LoggerFactory.getLogger(DomiZkManager.class); 58 | private static final int DEFAULT_SERVICE_PORT = 8080; 59 | private ZooKeeper zk = null; 60 | private static boolean inited = false; 61 | private static Lock LOCK = new ReentrantLock(); 62 | 63 | private AtomicBoolean online = new AtomicBoolean(true); 64 | 65 | 66 | @Resource 67 | private DynProps4FilesService dynProps4FilesService; 68 | 69 | @Resource 70 | private Environment env; 71 | 72 | /** 73 | * 节点数据变化监听处理器 74 | */ 75 | private static final ConcurrentHashMap nodeChangedHandlerHolder = new ConcurrentHashMap(); 76 | /** 77 | * 子节点变化监听处理器 78 | */ 79 | private static final ConcurrentHashMap childrenChangedHandlerHolder = new ConcurrentHashMap(); 80 | 81 | /** 82 | * 需处理的ZK事件 83 | */ 84 | private static volatile LinkedBlockingQueue zkEventQueue = new LinkedBlockingQueue(128); 85 | 86 | 87 | /** 88 | * ZK事件消费者 89 | */ 90 | private List consumers = new ArrayList(); 91 | 92 | /** 93 | * 事件消费者数量。同时该参数决定本机器最多可同时运行多少任务 94 | */ 95 | private int eventConsumerNum = 2; 96 | 97 | /** 98 | * zk 连接字符串 ip:port,ip1:port1,...,ipn:portn 99 | */ 100 | private String hostPort; 101 | 102 | /** 103 | * zk 会话超时时间(MS) 104 | */ 105 | private int sessionTimeout = 3600000; 106 | 107 | /** 108 | * zk 全局事件处理器 109 | */ 110 | private Watcher watcher = null; 111 | 112 | private CountDownLatch latch; 113 | 114 | /** 115 | * 自身节点监听器 116 | */ 117 | private DomiNodeHandler localMachineHandler; 118 | 119 | /** 120 | * 自身节点对象 121 | */ 122 | private MachineNode localMachine; 123 | 124 | /** 125 | * 本机节点zk路径 126 | */ 127 | private String nodePath; 128 | 129 | 130 | public DomiZkManager() { 131 | 132 | } 133 | 134 | 135 | /** 136 | * 连接ZK 发布本机节点 137 | */ 138 | @PostConstruct 139 | public void init() throws IOException { 140 | 141 | //构造本机节点 142 | if (localMachine == null) { 143 | 144 | localMachine = new MachineNode(); 145 | String ip = ""; 146 | ip = env.getProperty("LOCAL_HOST_NAME"); 147 | if (StringUtils.isBlank(ip)) { 148 | ip = getLocalIpAddress(); 149 | } 150 | localMachine.setHostname(ip); 151 | Integer port = null; 152 | port = env.getProperty("LOCAL_PORT", Integer.class); 153 | 154 | if (port == null) { 155 | port = DEFAULT_SERVICE_PORT; 156 | } 157 | localMachine.setPort(port); 158 | } 159 | 160 | if (watcher == null) { 161 | watcher = new DomiZkEventWatcher(this, localMachine); 162 | } 163 | 164 | 165 | boolean lockResult = LOCK.tryLock(); 166 | //防止多个线程同时调本方法,若发现已有线程正在调此方法,则直接返回 167 | if (!lockResult) { 168 | return; 169 | } 170 | 171 | 172 | this.hostPort = env.getProperty("ZK_HOSTS"); 173 | this.eventConsumerNum=env.getProperty("EVENT_CONSUMER_NUM",Integer.class); 174 | this.sessionTimeout=env.getProperty("SESSION_TIMEOUT",Integer.class); 175 | 176 | 177 | try { 178 | latch = new CountDownLatch(1); 179 | if (zk != null) { 180 | try { 181 | zk.close(); 182 | } catch (InterruptedException e) { 183 | //do nothing 184 | } 185 | } 186 | 187 | 188 | if (!inited) { 189 | nodePath = TC_MACHINE_LIST_PATH + PATH_SPLITTER + localMachine.nodeName(); 190 | Thread thread = null; 191 | EventConsumer ec = null; 192 | for (int i = 0; i < eventConsumerNum; i++) { 193 | ec = new EventConsumer(); 194 | thread = new Thread(ec, "ZK-Event-Consumer-" + i); 195 | thread.start(); 196 | //之所以把runnable加入LIST ,是为了在运行结束时,为EventConsumer 设置结束标志,让它正常结束线程 197 | consumers.add(ec); 198 | } 199 | 200 | Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { 201 | @Override 202 | public void run() { 203 | if (zk != null) { 204 | try { 205 | zk.close(); 206 | } catch (InterruptedException e) { 207 | //do nothing 208 | } 209 | } 210 | if (!CollectionUtils.isEmpty(consumers)) { 211 | //停止zk事件消费线程 212 | for (EventConsumer consumer : consumers) { 213 | consumer.shutdown = true; 214 | } 215 | } 216 | 217 | } 218 | })); 219 | 220 | //注册默认监听器 221 | if (localMachineHandler != null) { 222 | nodeChangedHandlerHolder 223 | .putIfAbsent(TC_MACHINE_LIST_PATH + PATH_SPLITTER + localMachine.nodeName(), localMachineHandler); 224 | } 225 | 226 | inited = true; 227 | } 228 | 229 | zk = new ZooKeeper(hostPort, sessionTimeout, watcher); 230 | 231 | if (StringUtils.equalsIgnoreCase(dynProps4FilesService.getProperty("CLOSE_ZK_CNN", "FALSE"), "TRUE")) { 232 | //仅在本地环境使用 233 | try { 234 | TimeUnit.SECONDS.sleep(1); 235 | } catch (InterruptedException e) { 236 | //do nothing 237 | } 238 | try { 239 | this.offlineLocalMachineNode(); 240 | } catch (Exception e) { 241 | logger.error("Offline failed.", e); 242 | } 243 | } 244 | 245 | 246 | } finally { 247 | LOCK.unlock(); 248 | } 249 | } 250 | 251 | 252 | /** 253 | * 等待建立连接 254 | */ 255 | @Override 256 | public void waitForConnected() { 257 | try { 258 | latch.await(); 259 | } catch (InterruptedException e) { 260 | logger.error("", e); 261 | } 262 | } 263 | 264 | @Override 265 | public void countDownWaitForConnectedLatch() { 266 | latch.countDown(); 267 | } 268 | 269 | /** 270 | * 追加监视的ZK事件 271 | * 272 | * @param event 273 | */ 274 | @Override 275 | public void addEvent(WatchedEvent event) { 276 | if (event == null) { 277 | logger.info("event is null"); 278 | return; 279 | } 280 | //在这里过滤不需要处理的事件 281 | String path = event.getPath(); 282 | if (!nodeChangedHandlerHolder.containsKey(path) && !childrenChangedHandlerHolder.containsKey(path)) { 283 | logger.info("No handler found.for path[{}]", path); 284 | return; 285 | } 286 | 287 | 288 | try { 289 | zkEventQueue.put(event); 290 | logger.info("event is add into zkEventQueue.EVENT[{}]", event); 291 | } catch (InterruptedException e) { 292 | logger.error("Adding zk event failed! [" + event + "]", e); 293 | } 294 | } 295 | 296 | public String getHostPort() { 297 | return hostPort; 298 | } 299 | 300 | public void setHostPort(String hostPort) { 301 | this.hostPort = hostPort; 302 | } 303 | 304 | public int getSessionTimeout() { 305 | return sessionTimeout; 306 | } 307 | 308 | public void setSessionTimeout(int sessionTimeout) { 309 | this.sessionTimeout = sessionTimeout; 310 | } 311 | 312 | public Watcher getWatcher() { 313 | return watcher; 314 | } 315 | 316 | public void setWatcher(Watcher watcher) { 317 | this.watcher = watcher; 318 | } 319 | 320 | /** 321 | * 发布非持久化节点 322 | * 323 | * @param path 324 | * @param value 325 | * @return 实际创建的节点路径 326 | * @throws KeeperException 327 | * @throws InterruptedException 328 | */ 329 | @Override 330 | public String createEphemeralNode(String path, String value) throws KeeperException, InterruptedException { 331 | 332 | try { 333 | return zk.create(path, value.getBytes(DEFAULT_CHARSET), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); 334 | } catch (UnsupportedEncodingException e) { 335 | //unreachable 336 | return null; 337 | } 338 | } 339 | 340 | 341 | /** 342 | * 发布持久化节点 343 | * 344 | * @param path 345 | * @param value 346 | * @return 实际创建的节点路径 347 | * @throws KeeperException 348 | * @throws InterruptedException 349 | */ 350 | @Override 351 | public String createNode(String path, String value) throws KeeperException, InterruptedException { 352 | waitForConnected(); 353 | 354 | try { 355 | return zk.create(path, value.getBytes(DEFAULT_CHARSET), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 356 | } catch (UnsupportedEncodingException e) { 357 | //unreachable 358 | return null; 359 | } 360 | } 361 | 362 | /** 363 | * 取当前节点路径的父路径。 364 | * 365 | * @param nodePath 366 | * @return 父路径。当前节点为"/"时,则返回null. 367 | */ 368 | private static String getParentPath(String nodePath) { 369 | if (StringUtils.isBlank(nodePath)) { 370 | throw new IllegalArgumentException("Param nodePath should not be blank!"); 371 | } 372 | if (StringUtils.equals(PATH_SPLITTER, StringUtils.trim(nodePath))) { 373 | return null; 374 | } 375 | 376 | return nodePath.substring(0, nodePath.lastIndexOf(PATH_SPLITTER)); 377 | } 378 | 379 | 380 | /** 381 | * 创建节点。若父节点不存在则先创建父节点 382 | * 383 | * @param path 384 | * @param value 385 | * @return 386 | * @throws KeeperException 387 | * @throws InterruptedException 388 | */ 389 | public String createNodeRecursively(String path, String value) throws KeeperException, InterruptedException { 390 | waitForConnected(); 391 | String parentPath = getParentPath(path); 392 | 393 | if (!exists(parentPath)) { 394 | createNodeRecursively(parentPath, ""); 395 | } 396 | 397 | return createNode(path, value); 398 | } 399 | 400 | /** 401 | * @param path 402 | * @param value 403 | * @return 404 | * @throws KeeperException 405 | * @throws InterruptedException 406 | */ 407 | @Override 408 | public Stat updateNodeData(String path, String value) throws KeeperException, InterruptedException { 409 | return updateNodeData(path, value, -1); 410 | } 411 | 412 | /** 413 | * 创建节点信息,若该节点已存在则更新此节点数据 414 | * 415 | * @param path 416 | * @param value 417 | */ 418 | @Override 419 | public void createOrUpdateNodeData(String path, String value) throws KeeperException, InterruptedException { 420 | if (exists(path)) { 421 | updateNodeData(path, value); 422 | } else { 423 | createNodeRecursively(path, value); 424 | } 425 | } 426 | 427 | /** 428 | * 判断节点是否存在 429 | * 430 | * @param path 431 | * @return 432 | * @throws KeeperException 433 | * @throws InterruptedException 434 | */ 435 | @Override 436 | public boolean exists(String path) throws KeeperException, InterruptedException { 437 | return exists(path, false); 438 | } 439 | 440 | /** 441 | * 判断节点是否存在 442 | * 443 | * @param path 444 | * @param watch 监听此节点create 、delete事件 445 | * @return 446 | * @throws org.apache.zookeeper.KeeperException 447 | * @throws InterruptedException 448 | */ 449 | @Override 450 | public boolean exists(String path, boolean watch) throws KeeperException, InterruptedException { 451 | waitForConnected(); 452 | Stat stat = zk.exists(path, watch); 453 | return (stat != null); 454 | } 455 | 456 | /** 457 | * 删除节点 458 | * 459 | * @param path 460 | * @throws KeeperException 461 | * @throws InterruptedException 462 | */ 463 | @Override 464 | public void deleteNode(String path) throws KeeperException, InterruptedException { 465 | waitForConnected(); 466 | zk.delete(path, -1); 467 | } 468 | 469 | 470 | /** 471 | * 递归删除节点 472 | * 473 | * @param path 474 | */ 475 | @Override 476 | public void deleteNodeRecursively(String path) throws KeeperException, InterruptedException { 477 | waitForConnected(); 478 | if (StringUtils.equals(PATH_SPLITTER, StringUtils.trim(path))) { 479 | return; 480 | } 481 | List children = getChildren(path); 482 | if (children != null && !children.isEmpty()) { 483 | for (String child : children) { 484 | deleteNodeRecursively(path + PATH_SPLITTER + child); 485 | } 486 | } 487 | 488 | deleteNode(path); 489 | } 490 | 491 | /** 492 | * 为特定路径的节点 添加监听器(同时监听数据变化以及该节点存在性变化) 493 | * 494 | * @param path 495 | * @param handler 496 | */ 497 | @Override 498 | public void addNodeDataChangedHandler(String path, 499 | DomiNodeHandler handler) throws KeeperException, InterruptedException { 500 | if (StringUtils.isBlank(path)) { 501 | throw new IllegalArgumentException("Handling path for node is BLANK!"); 502 | } 503 | Object previous = nodeChangedHandlerHolder.put(path, handler); 504 | if (previous == null) { 505 | //注册监听 506 | getData(path, true); 507 | exists(path, true); 508 | } 509 | } 510 | 511 | /** 512 | * 为特定路径的节点 添加监听器(监听数子节点数量变化) 513 | * 514 | * @param path 515 | * @param handler 516 | */ 517 | @Override 518 | public void addChildrenChangedHandler(String path, 519 | DomiChildrenNodeHandler handler) throws KeeperException, InterruptedException { 520 | if (StringUtils.isBlank(path)) { 521 | throw new IllegalArgumentException("Handling path for children is BLANK!"); 522 | } 523 | Object previous = childrenChangedHandlerHolder.put(path, handler); 524 | if (previous == null) { 525 | //注册监听 526 | getChildren(path, true); 527 | } 528 | } 529 | 530 | /** 531 | * 追加子节点监听器(初始化前调用有效) 532 | * 533 | * @param childrenNodeHandlerMap 534 | */ 535 | @Override 536 | public void setChildrenChangedHandlerBeforeInit( 537 | Map childrenNodeHandlerMap) { 538 | 539 | childrenChangedHandlerHolder.putAll(childrenNodeHandlerMap); 540 | } 541 | 542 | 543 | /** 544 | * 追加节点监听器(初始化前调用有效) 545 | * 546 | * @param nodeHandlerMap 547 | */ 548 | @Override 549 | public void setNodeDataChangedHandlerBeforeInit(Map nodeHandlerMap) { 550 | nodeChangedHandlerHolder.putAll(nodeHandlerMap); 551 | } 552 | 553 | /** 554 | * 获取节点数据 555 | * 556 | * @param path 557 | * @return 558 | */ 559 | @Override 560 | public String getData(String path) throws KeeperException, InterruptedException { 561 | waitForConnected(); 562 | return getData(path, false); 563 | } 564 | 565 | /** 566 | * 更新node数据 567 | * 568 | * @param path 569 | * @param value 570 | * @param version 571 | * @return 572 | * @throws org.apache.zookeeper.KeeperException 573 | * @throws InterruptedException 574 | */ 575 | @Override 576 | public Stat updateNodeData(String path, String value, int version) throws KeeperException, InterruptedException { 577 | waitForConnected(); 578 | try { 579 | return zk.setData(path, value.getBytes(DEFAULT_CHARSET), version); 580 | } catch (UnsupportedEncodingException e) { 581 | //unreachable 582 | return null; 583 | } 584 | } 585 | 586 | /** 587 | * 获取节点数据 588 | * 589 | * @param path 590 | * @param stat 591 | * @return 592 | * @throws InterruptedException 593 | * @throws org.apache.zookeeper.KeeperException 594 | */ 595 | @Override 596 | public String getData(String path, Stat stat) throws KeeperException, InterruptedException { 597 | return getData(path, false, stat); 598 | } 599 | 600 | /** 601 | * 获取节点数据 602 | * 603 | * @param path 604 | * @param watch true:监听此节点的数据变化 605 | * @return 606 | */ 607 | @Override 608 | public String getData(String path, boolean watch) throws KeeperException, InterruptedException { 609 | return getData(path, watch, null); 610 | } 611 | 612 | /** 613 | * 获取节点数据 614 | * 615 | * @param path 616 | * @param watch true:监听此节点的数据变化 617 | * @param stat 618 | * @return 619 | * @throws org.apache.zookeeper.KeeperException 620 | * @throws InterruptedException 621 | */ 622 | @Override 623 | public String getData(String path, boolean watch, Stat stat) throws KeeperException, InterruptedException { 624 | waitForConnected(); 625 | byte[] data = new byte[0]; 626 | try { 627 | data = zk.getData(path, watch, stat); 628 | } catch (KeeperException e) { 629 | if (e instanceof KeeperException.NoNodeException) { 630 | return null; 631 | } 632 | throw e; 633 | } 634 | try { 635 | return new String(data, DEFAULT_CHARSET); 636 | } catch (UnsupportedEncodingException e) { 637 | //unreachable 638 | return null; 639 | } 640 | } 641 | 642 | /** 643 | * 获取path节点的子节点名称列表 644 | * 645 | * @param path 646 | * @return 647 | */ 648 | @Override 649 | public List getChildren(String path) throws KeeperException, InterruptedException { 650 | return getChildren(path, false); 651 | } 652 | 653 | /** 654 | * 获取path节点的子节点名称列表 655 | * 656 | * @param path 657 | * @param watch 监听节点个数变化的事件 658 | * @return 659 | */ 660 | @Override 661 | public List getChildren(String path, boolean watch) throws KeeperException, InterruptedException { 662 | waitForConnected(); 663 | try { 664 | return zk.getChildren(path, watch); 665 | } catch (KeeperException e) { 666 | if (e instanceof KeeperException.NoNodeException) { 667 | logger.warn("", e); 668 | return null; 669 | } 670 | throw e; 671 | } 672 | } 673 | 674 | /** 675 | * 移除特定路径节点的监听器 676 | * 677 | * @param path 678 | */ 679 | @Override 680 | public void removeNodeDataChangedHandler(String path) { 681 | if (path == null) { 682 | return; 683 | } 684 | 685 | nodeChangedHandlerHolder.remove(path); 686 | } 687 | 688 | /** 689 | * 移除特定路径节点的子节点处理器 690 | * 691 | * @param path 692 | */ 693 | @Override 694 | public void removeChildrenChangedHandler(String path) { 695 | if (path == null) { 696 | return; 697 | } 698 | childrenChangedHandlerHolder.remove(path); 699 | } 700 | 701 | /** 702 | * 移除特定路径节点的所有处理器 703 | * 704 | * @param path 705 | */ 706 | @Override 707 | public void removeHandlerByPath(String path) { 708 | removeChildrenChangedHandler(path); 709 | removeNodeDataChangedHandler(path); 710 | } 711 | 712 | /** 713 | * 获取本机节点对象 714 | * 715 | * @return 716 | */ 717 | @Override 718 | public MachineNode getLocalMachine() { 719 | return this.localMachine; 720 | } 721 | 722 | 723 | /** 724 | * 从ZK端获取本机节点对象 725 | * 726 | * @return 727 | */ 728 | @Override 729 | public MachineNode getLocalMachineFromZk() { 730 | 731 | MachineNode zkNode = null; 732 | try { 733 | Stat stat = new Stat(); 734 | String value = getData(ZkManager.TC_MACHINE_LIST_PATH + PATH_SPLITTER + localMachine.nodeName(), stat); 735 | zkNode = simpleObjectMapper.readValue(value, MachineNode.class); 736 | 737 | } catch (Exception e) { 738 | logger.error("Get local machine value from zk is failed! ", e); 739 | } 740 | return zkNode; 741 | } 742 | 743 | /** 744 | * 判断是否有 path 节点的子节点监听处理器 745 | * 746 | * @param path 747 | */ 748 | @Override 749 | public boolean containChildrenChangedHandler(String path) { 750 | return childrenChangedHandlerHolder.containsKey(path); 751 | } 752 | 753 | /** 754 | * 判断是否有 path 节点的监听处理器 755 | * 756 | * @param path 757 | * @return 758 | */ 759 | @Override 760 | public boolean containNodeDataChangedHandler(String path) { 761 | return nodeChangedHandlerHolder.containsKey(path); 762 | } 763 | 764 | /** 765 | * 重新注册所有节点监听器 766 | */ 767 | @Override 768 | public void refreshAllNodeDataChangedHandlers() { 769 | waitForConnected(); 770 | for (String path : nodeChangedHandlerHolder.keySet()) { 771 | try { 772 | exists(path, true); 773 | getData(path, true); 774 | } catch (Exception e) { 775 | logger.error("Watching node changed failed! path[{}]", path, e); 776 | } 777 | } 778 | logger.warn("Refreshing all data-changed handlers completed! {}", nodeChangedHandlerHolder); 779 | } 780 | 781 | /** 782 | * 重新注册所有子节点监听器 783 | */ 784 | @Override 785 | public void refreshAllChildrenChangedHandlers() { 786 | waitForConnected(); 787 | for (String path : childrenChangedHandlerHolder.keySet()) { 788 | try { 789 | getChildren(path, true); 790 | } catch (Exception e) { 791 | logger.error("Watching node children changed failed! path[{}]", path, e); 792 | } 793 | } 794 | logger.warn("Refreshing all children-changed handlers completed! {}", childrenChangedHandlerHolder); 795 | } 796 | 797 | public DomiNodeHandler getLocalMachineHandler() { 798 | return localMachineHandler; 799 | } 800 | 801 | public void setLocalMachineHandler(DomiNodeHandler localMachineHandler) { 802 | this.localMachineHandler = localMachineHandler; 803 | } 804 | 805 | public int getEventConsumerNum() { 806 | return eventConsumerNum; 807 | } 808 | 809 | public void setEventConsumerNum(int eventConsumerNum) { 810 | this.eventConsumerNum = eventConsumerNum; 811 | } 812 | 813 | 814 | /** 815 | * 下线本机节点 816 | */ 817 | @Override 818 | public void offlineLocalMachineNode() throws KeeperException, InterruptedException { 819 | this.online.set(false); 820 | //仅根据本机节点是否在线上进行判断 821 | if (this.exists(nodePath)) { 822 | this.deleteNode(nodePath); 823 | } 824 | } 825 | 826 | /** 827 | * 上线本机节点 828 | */ 829 | @Override 830 | public void onlineLocalMachineNode() throws KeeperException, InterruptedException, IOException { 831 | if (!this.exists(nodePath)) { 832 | this.createEphemeralNode(nodePath, simpleObjectMapper.writeValueAsString(localMachine)); 833 | } 834 | this.online.set(true); 835 | } 836 | 837 | /** 838 | * 判断本机节点是否已在zk上注册 839 | * 840 | * @return 841 | */ 842 | @Override 843 | public boolean isLocalMachineOnline() { 844 | return this.online.get(); 845 | } 846 | 847 | /** 848 | * 事件处理器 849 | */ 850 | class EventConsumer implements Runnable { 851 | private volatile boolean shutdown = false; 852 | 853 | @Override 854 | public void run() { 855 | String dataPath = null;//节点路径 856 | while (!shutdown) { 857 | WatchedEvent event = null; 858 | try { 859 | event = zkEventQueue.poll(2L, TimeUnit.SECONDS); 860 | } catch (InterruptedException e) { 861 | //do nothing 862 | } 863 | if (event == null) { 864 | continue; 865 | } 866 | String value = null; 867 | DomiNodeHandler handler = null; 868 | List childrenNameList = null; 869 | dataPath = event.getPath(); 870 | if (StringUtils.isBlank(dataPath)) { 871 | continue; 872 | } 873 | try { 874 | switch (event.getType()) { 875 | case NodeDataChanged: 876 | 877 | handler = nodeChangedHandlerHolder.get(dataPath); 878 | if (handler != null) { 879 | value = DomiZkManager.this.getData(dataPath, true); 880 | handler.handleDataChanged(dataPath, value, DomiZkManager.this); 881 | } 882 | break; 883 | case NodeCreated: 884 | handler = nodeChangedHandlerHolder.get(dataPath); 885 | if (handler != null) { 886 | DomiZkManager.this.exists(dataPath, true); 887 | value = DomiZkManager.this.getData(dataPath, false); 888 | handler.handleNodeCreated(dataPath, value, DomiZkManager.this); 889 | } 890 | break; 891 | case NodeDeleted: 892 | handler = nodeChangedHandlerHolder.get(dataPath); 893 | if (handler != null) { 894 | DomiZkManager.this.exists(dataPath, true); 895 | handler.handleNodeDeleted(dataPath, DomiZkManager.this); 896 | } 897 | break; 898 | case NodeChildrenChanged: 899 | DomiChildrenNodeHandler childrenNodeHandler = childrenChangedHandlerHolder.get( 900 | dataPath); 901 | if (childrenNodeHandler != null) { 902 | childrenNameList = DomiZkManager.this.getChildren(dataPath, true); 903 | childrenNodeHandler 904 | .handleChildrenChanged(dataPath, childrenNameList, DomiZkManager.this); 905 | } 906 | break; 907 | default: 908 | break; 909 | } 910 | } catch (Exception e) { 911 | logger.error("执行处理器失败。ZK事件:", event.toString(), e); 912 | } 913 | 914 | } 915 | } 916 | } 917 | 918 | /** 919 | * 获取 本机非127.0.0.1 ipv4地址 920 | * 921 | * @return 922 | */ 923 | private String getLocalIpAddress() { 924 | try { 925 | Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces(); 926 | NetworkInterface element = null; 927 | InetAddress addr = null; 928 | while (allNetInterfaces.hasMoreElements()) { 929 | element = allNetInterfaces.nextElement(); 930 | Enumeration addrs = element.getInetAddresses(); 931 | 932 | while (addrs.hasMoreElements()) { 933 | addr = addrs.nextElement(); 934 | if (addr == null) { 935 | continue; 936 | } 937 | if (addr instanceof Inet4Address && !(StringUtils.equals("127.0.0.1", addr.getHostAddress()) || StringUtils.equals("0.0.0.0", addr.getHostAddress()))) { 938 | return addr.getHostAddress(); 939 | } 940 | } 941 | } 942 | } catch (SocketException e) { 943 | logger.error("Getting localhost ip failed.", e); 944 | return ""; 945 | } 946 | return ""; 947 | } 948 | } 949 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/zookeeper/watcher/DomiChildrenNodeHandler.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.zookeeper.watcher; 2 | 3 | import com.company.project.core.service.zookeeper.ZkManager; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 子节点变化处理器 9 | * 10 | * @author liwei 11 | * 16-10-27 下午1:39 12 | */ 13 | public interface DomiChildrenNodeHandler { 14 | 15 | 16 | /** 17 | * 节点变化(增加或删除节点)后的处理 18 | * 19 | * @param path 20 | * @param value 21 | */ 22 | Object handleChildrenChanged(String path, List value, ZkManager zkManager); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/zookeeper/watcher/DomiNodeHandler.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.zookeeper.watcher; 2 | 3 | import com.company.project.core.service.zookeeper.ZkManager; 4 | 5 | /** 6 | * zk数据节点处理器 7 | * 8 | * @author qingren.lw 9 | * 14-9-14 下午9:02 10 | */ 11 | public interface DomiNodeHandler { 12 | 13 | 14 | /** 15 | * 增加节点后的处理 16 | * 17 | * @param path 18 | * @param value 19 | * @return 20 | */ 21 | Object handleNodeCreated(String path, String value, ZkManager zkManager); 22 | 23 | /** 24 | * 节点数据变化(修改节点数据)后的处理 25 | * 26 | * @param path 27 | * @param value 28 | * @return 29 | */ 30 | Object handleDataChanged(String path, String value, ZkManager zkManager); 31 | 32 | /** 33 | * 删除节点后的处理 34 | * 35 | * @param path 36 | * @return 37 | */ 38 | Object handleNodeDeleted(String path, ZkManager zkManager); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/service/zookeeper/watcher/DomiZkEventWatcher.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.zookeeper.watcher; 2 | 3 | import com.company.project.core.service.zookeeper.MachineNode; 4 | import com.company.project.core.service.zookeeper.ZkManager; 5 | import org.apache.zookeeper.KeeperException; 6 | import org.apache.zookeeper.WatchedEvent; 7 | import org.apache.zookeeper.Watcher; 8 | import org.codehaus.jackson.map.ObjectMapper; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | 14 | /** 15 | * zk事件监视器 16 | * 17 | * @author qingren.lw 18 | * 14-9-12 下午5:29 19 | */ 20 | public class DomiZkEventWatcher implements Watcher { 21 | private static final Logger logger = LoggerFactory.getLogger(DomiZkEventWatcher.class); 22 | private ZkManager manager; 23 | private static boolean rootCreated = false; 24 | 25 | 26 | /** 27 | * 本机节点 28 | */ 29 | private MachineNode localMachine; 30 | 31 | public DomiZkEventWatcher(ZkManager manager, MachineNode localMachine) { 32 | this.localMachine = localMachine; 33 | this.manager = manager; 34 | 35 | } 36 | 37 | /** 38 | * zk 事件处理器 39 | * 40 | * @param event 41 | */ 42 | @Override 43 | public void process(WatchedEvent event) { 44 | logger.info("Received event:{}", event); 45 | 46 | if (event.getState() == Event.KeeperState.Expired) { 47 | //若ZK出现超时,则需重新初始化ZK连接 48 | try { 49 | logger.warn("Zookeeper session expired.Reconnecting..."); 50 | processSessionExpired(); 51 | 52 | } catch (IOException e) { 53 | logger.error("Re-connect zookeeper failed.", e); 54 | throw new RuntimeException(e);//此处抛个异常,为阻止后续流程 55 | } 56 | } 57 | if (event.getType() == Event.EventType.None) { 58 | if (event.getState() == Event.KeeperState.SyncConnected) { 59 | processSyncConnected(); 60 | logger.warn("Zookeeper connected."); 61 | //连接成功后,直接发起一个任务节点变化事件,以防止系统 down掉后,任务变成僵尸节点 62 | resumeEvent(); 63 | } 64 | return; 65 | } 66 | 67 | manager.addEvent(event); 68 | 69 | } 70 | 71 | 72 | /** 73 | * 恢复节点监听事件 74 | */ 75 | private void resumeEvent() { 76 | //此方法暂预留 77 | 78 | 79 | ////构造模型任务子节点变化事件 80 | //WatchedEvent event = new WatchedEvent(Event.EventType.NodeChildrenChanged, Event.KeeperState.SyncConnected, 81 | // ZkManager.EBAY_MODEL_TASKS_ROOT_PATH); 82 | //manager.addEvent(event); 83 | ////构造批处理任务子节点变化 84 | //event = new WatchedEvent(Event.EventType.NodeChildrenChanged, Event.KeeperState.SyncConnected, 85 | // ZkManager.EBAY_BATCH_TASKS_ROOT_PATH); 86 | //manager.addEvent(event); 87 | 88 | 89 | } 90 | 91 | /** 92 | * 会话超时处理:重新初始化zkManager 93 | */ 94 | private void processSessionExpired() throws IOException { 95 | manager.init(); 96 | } 97 | 98 | /** 99 | * 建立连接后的处理 100 | */ 101 | private void processSyncConnected() { 102 | manager.countDownWaitForConnectedLatch(); 103 | ObjectMapper om = new ObjectMapper(); 104 | try { 105 | createRootNode(); 106 | if (localMachine != null) { 107 | //若服务处于上线状态,则重新发布本机节点 108 | 109 | if(manager.isLocalMachineOnline()){ 110 | manager.onlineLocalMachineNode(); 111 | } 112 | 113 | //String machineNodeValue = om.writeValueAsString(localMachine); 114 | ////发布非持久结点(自身) 115 | //String selfNodePath = ZkManager.TC_MACHINE_LIST_PATH + ZkManager.PATH_SPLITTER + localMachine.nodeName(); 116 | //if (!manager.exists(selfNodePath)) { 117 | // manager.createEphemeralNode(selfNodePath, machineNodeValue); 118 | //} 119 | } 120 | //刷新所有监听器 121 | manager.refreshAllChildrenChangedHandlers(); 122 | manager.refreshAllNodeDataChangedHandlers(); 123 | 124 | } catch (Exception e) { 125 | logger.error("Initializing node failed.", e); 126 | } 127 | 128 | } 129 | 130 | 131 | /** 132 | * 创建根节点 133 | * 134 | * @throws KeeperException 135 | * @throws InterruptedException 136 | */ 137 | private void createRootNode() throws KeeperException, InterruptedException { 138 | if (rootCreated) { 139 | return; 140 | } 141 | 142 | synchronized (this.getClass()) { 143 | if (rootCreated) { 144 | return; 145 | } 146 | 147 | //发布各根结点 148 | if (!manager.exists(ZkManager.TC_ROOT_PATH)) { 149 | manager.createNode(ZkManager.TC_ROOT_PATH, ""); 150 | } 151 | if (!manager.exists(ZkManager.TC_MACHINE_LIST_PATH)) { 152 | manager.createNode(ZkManager.TC_MACHINE_LIST_PATH, ""); 153 | } 154 | rootCreated = true; 155 | } 156 | } 157 | 158 | 159 | public MachineNode getLocalMachine() { 160 | return localMachine; 161 | } 162 | 163 | public void setLocalMachine(MachineNode localMachine) { 164 | this.localMachine = localMachine; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/utils/ProjectByteUtils.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.utils; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | 5 | /** 6 | * 字节数组相关工具类 7 | * 8 | */ 9 | public class ProjectByteUtils { 10 | /** 11 | * 16进制单字节最大值 12 | */ 13 | public static final int HEX_UNIT = 0xff; 14 | /** 15 | * 数字单字节所占的位数 16 | */ 17 | private static final int NUM_BIT_UNIT = 8; 18 | 19 | private static final String DEFAULT_CHARSET = "UTF-8"; 20 | 21 | /** 22 | * Hash 乘法因子 23 | */ 24 | private static final int HASH_ELEMENT = 16777619; 25 | 26 | /** 27 | * int 数据转字节数组 28 | * 29 | * @param value 30 | * 31 | * @return 32 | */ 33 | public static byte[] getBytes(int value) { 34 | int byteLength = 4;// int 4 字节 35 | byte[] data = new byte[byteLength]; 36 | int index = 0; 37 | //处理 type 38 | for (int i = 0; i < byteLength; i++) { 39 | data[index++] = (byte) ((value >> (i * NUM_BIT_UNIT)) & HEX_UNIT); 40 | } 41 | return data; 42 | } 43 | 44 | // /** 45 | // * Integer 数据转字节数组 46 | // * 47 | // * @param value 不可为null 48 | // * 49 | // * @return 50 | // */ 51 | // public static byte[] getBytesFromInt(Integer value) { 52 | // if (value == null) { 53 | // throw new IllegalArgumentException("Param value is null"); 54 | // } 55 | // return getBytesFromInt(value.intValue()); 56 | // } 57 | 58 | /** 59 | * long 数据转字节数组 60 | * 61 | * @param value 62 | * 63 | * @return 64 | */ 65 | public static byte[] getBytes(long value) { 66 | int byteLength = 8;// long 8 字节 67 | byte[] data = new byte[byteLength]; 68 | int index = 0; 69 | //处理 type 70 | for (int i = 0; i < byteLength; i++) { 71 | data[index++] = (byte) ((value >> (i * NUM_BIT_UNIT)) & HEX_UNIT); 72 | } 73 | return data; 74 | } 75 | 76 | /** 77 | * double 数据转字节数组 78 | * 79 | * @param value 80 | * 81 | * @return 82 | */ 83 | public static byte[] getBytes(double value) { 84 | long lonValue = Double.doubleToLongBits(value); 85 | return getBytes(lonValue); 86 | } 87 | 88 | /** 89 | * float 数据转字节数组 90 | * 91 | * @param value 92 | * 93 | * @return 94 | */ 95 | public static byte[] getBytes(float value) { 96 | int intValue = Float.floatToIntBits(value); 97 | return getBytes(intValue); 98 | } 99 | 100 | /** 101 | * String 数据转字节数组 102 | * 103 | * @param value 104 | * @param charset 105 | * 106 | * @return 107 | */ 108 | public static byte[] getBytes(String value, String charset) throws UnsupportedEncodingException { 109 | if (value == null) { 110 | throw new IllegalArgumentException("Param value is null"); 111 | } 112 | return value.getBytes(charset); 113 | } 114 | 115 | /** 116 | * String 数据转字节数组(使用UTF-8字符编码集) 117 | * 118 | * @param value 119 | * 120 | * @return 121 | */ 122 | public static byte[] getBytes(String value) throws UnsupportedEncodingException { 123 | return getBytes(value, DEFAULT_CHARSET); 124 | } 125 | 126 | 127 | /** 128 | * 计算hash值 129 | * 130 | * @param data 131 | * 132 | * @return 133 | */ 134 | public static int computeHash(byte[] data) { 135 | //基准值 随意质数 136 | int hash = (int) 2166136261L; 137 | 138 | 139 | for (byte b : data) { 140 | hash = (hash ^ b) * HASH_ELEMENT; 141 | } 142 | hash += hash << 13; 143 | hash ^= hash >> 7; 144 | hash += hash << 3; 145 | hash ^= hash >> 17; 146 | hash += hash << 5; 147 | 148 | return hash; 149 | } 150 | 151 | 152 | /** 153 | * 计算hash值 154 | * 155 | * @param sourceDataArrays 156 | * 157 | * @return 158 | */ 159 | public static int computeHash(byte[]... sourceDataArrays) { 160 | int totalLength = 0; 161 | for (byte[] source : sourceDataArrays) { 162 | totalLength += source.length; 163 | } 164 | if (totalLength == 0) { 165 | return 0; 166 | } 167 | 168 | byte[] data = new byte[totalLength]; 169 | 170 | int index = 0; 171 | for (byte[] source : sourceDataArrays) { 172 | for (byte b : source) { 173 | data[index++] = b; 174 | } 175 | 176 | } 177 | return computeHash(data); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/utils/ProjectDateUtils.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.utils; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.text.ParseException; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Calendar; 10 | import java.util.Date; 11 | 12 | /** 13 | * 日期相关工具类 14 | */ 15 | public class ProjectDateUtils { 16 | private static final Logger logger = LoggerFactory.getLogger(ProjectDateUtils.class); 17 | 18 | public static final String DF_YYYYMMDD = "yyyyMMdd"; 19 | public static final String DF_YYYY_MM_DD = "yyyy-MM-dd"; 20 | public static final String DF_YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; 21 | public static final String DF_YYYY_MM_DDHHMMSS = "yyyy-MM-dd HH:mm:ss"; 22 | public static final String DF_HHMMSS = "HHmmss"; 23 | 24 | 25 | /** 26 | * 将日期转换成字符串 27 | * 28 | * @param date 29 | * @param format 30 | * 31 | * @return 32 | */ 33 | public static String format(Date date, String format) { 34 | if (date == null) { 35 | throw new IllegalArgumentException("Param date is null!"); 36 | } 37 | if (StringUtils.isBlank(format)) { 38 | throw new IllegalArgumentException("Param format is blank!"); 39 | } 40 | SimpleDateFormat sdf = new SimpleDateFormat(format); 41 | return sdf.format(date); 42 | } 43 | 44 | /** 45 | * 将日期转换成yyyyMMddHHmmss字符串 46 | * 47 | * @param date 48 | * 49 | * @return 50 | */ 51 | public static String format14(Date date) { 52 | return format(date, DF_YYYYMMDDHHMMSS); 53 | } 54 | 55 | public static String format8(Date date) { 56 | return format(date, DF_YYYYMMDD); 57 | } 58 | 59 | public static String format10(Date date) { 60 | return format(date, DF_YYYY_MM_DD); 61 | } 62 | 63 | public static Date parse8(String dateStr) throws ParseException { 64 | return parse(dateStr, DF_YYYYMMDD); 65 | } 66 | 67 | public static Date parse10(String dateStr) throws ParseException { 68 | return parse(dateStr, DF_YYYY_MM_DD); 69 | } 70 | 71 | /** 72 | * 返回当前日期 yyyyMMdd格式 字符串 73 | * 74 | * @return 75 | */ 76 | public static String getNow8() { 77 | return format8(new Date()); 78 | } 79 | 80 | /** 81 | * 返回当前日期 yyyyMMdd格式 字符串 82 | * 83 | * @return 84 | */ 85 | public static String getNow10() { 86 | return format10(new Date()); 87 | } 88 | 89 | /** 90 | * 获取当天0点 date对象 91 | * @return 92 | */ 93 | public static Date getToday(){ 94 | Date today=null; 95 | try { 96 | today =parse(getNow8(),DF_YYYYMMDD); 97 | } catch (ParseException e) { 98 | //unreachable 99 | logger.error("Parsing / Getting today's date failed.",e); 100 | today = new Date(); 101 | } 102 | return today; 103 | } 104 | 105 | /** 106 | * 获取当年1月1日0点 date对象 107 | * @return 108 | */ 109 | public static Date getNowYear(){ 110 | Date year=null; 111 | try { 112 | String format = "yyyy"; 113 | year =parse(format(new Date(),format),format); 114 | } catch (ParseException e) { 115 | //unreachable 116 | logger.error("Parsing / Getting year's date failed.",e); 117 | year = new Date(); 118 | } 119 | return year; 120 | } 121 | 122 | /** 123 | * 获取当月1号0点 date对象 124 | * @return 125 | */ 126 | public static Date getNowMonth(){ 127 | Date month=null; 128 | try { 129 | String format = "yyyy-MM"; 130 | month =parse(format(new Date(),format),format); 131 | } catch (ParseException e) { 132 | //unreachable 133 | logger.error("Parsing / Getting month's date failed.",e); 134 | month = new Date(); 135 | } 136 | return month; 137 | } 138 | 139 | public static int getMonthSpace(Date date1, Date date2) { 140 | Calendar c1 = Calendar.getInstance(); 141 | Calendar c2 = Calendar.getInstance(); 142 | c1.setTime(date1); 143 | c2.setTime(date2); 144 | int result = c2.get(Calendar.YEAR) - c1.get(Calendar.YEAR); 145 | return result == 0 ? 1 : Math.abs(result); 146 | } 147 | 148 | public static int getYearSpace4Now(String date) throws ParseException { 149 | Calendar c1 = Calendar.getInstance(); 150 | Calendar c2 = Calendar.getInstance(); 151 | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); 152 | c1.setTime(new Date()); 153 | c2.setTime(sdf.parse(date)); 154 | int result = c2.get(Calendar.YEAR) - c1.get(Calendar.YEAR); 155 | return result == 0 ? 1 : Math.abs(result); 156 | } 157 | 158 | public static Date parse(String dateStr, String format) throws ParseException { 159 | SimpleDateFormat sdf = new SimpleDateFormat(format); 160 | return sdf.parse(dateStr); 161 | } 162 | 163 | /** 164 | * @return 获取当前月第一天: 165 | */ 166 | public static Date getFirstDateOfCurrentMonth() { 167 | Calendar c = Calendar.getInstance(); 168 | c.add(Calendar.MONTH, 0); 169 | c.set(Calendar.DAY_OF_MONTH, 1);//设置为1号,当前日期既为本月第一天 170 | try { 171 | return parse(format8(c.getTime()),DF_YYYYMMDD); 172 | } catch (ParseException e) { 173 | logger.error("Parsing first day of current month is faile.",e); 174 | return null; 175 | } 176 | 177 | } 178 | 179 | /** 180 | * @return 获取下月第一天: 181 | */ 182 | public static Date getFirstDateOfNextMonth() { 183 | Calendar c = Calendar.getInstance(); 184 | c.add(Calendar.MONTH, 1); 185 | c.set(Calendar.DAY_OF_MONTH, 1);//设置为1号,当前日期既为本月第一天 186 | try { 187 | return parse(format8(c.getTime()),DF_YYYYMMDD); 188 | } catch (ParseException e) { 189 | logger.error("Parsing first day of current month is faile.",e); 190 | return null; 191 | } 192 | 193 | } 194 | 195 | /** 196 | * @return 获取当前月最后一天 197 | */ 198 | public static Date getListDateOfCurrentMonth() { 199 | Calendar c = Calendar.getInstance(); 200 | c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH)); 201 | try { 202 | return parse(format8(c.getTime()),DF_YYYYMMDD); 203 | } catch (ParseException e) { 204 | logger.error("Parsing first day of current month is faile.",e); 205 | return null; 206 | } 207 | } 208 | 209 | /** 210 | * @return 当月的总天数 211 | */ 212 | public static int getCurrentMonthDay() { 213 | Calendar a = Calendar.getInstance(); 214 | a.set(Calendar.DATE, 1); 215 | a.roll(Calendar.DATE, -1); 216 | int maxDate = a.get(Calendar.DATE); 217 | return maxDate; 218 | } 219 | 220 | /** 221 | * 计算时间间隔 222 | * 223 | * @param fDate 上次时间 224 | * @param oDate 本次时间 225 | * @return 精确到天 226 | */ 227 | public static int getIntervalDays(Date fDate, Date oDate) { 228 | if (fDate == null || oDate == null) { 229 | return -1; 230 | } 231 | try { 232 | fDate = parse(format8(fDate),DF_YYYYMMDD); 233 | oDate = parse(format8(oDate), DF_YYYYMMDD); 234 | } catch (ParseException e) { 235 | logger.error("Parsing interval days is faile.", e); 236 | } 237 | long nd = 1000*24*60*60;//一天的毫秒数 238 | //获得两个时间的毫秒时间差异 239 | long diff = oDate.getTime() - fDate.getTime(); 240 | Long day = diff/nd;//计算差多少天 241 | return day.intValue(); 242 | 243 | } 244 | 245 | /** 246 | * 247 | * @param date 248 | * @return 249 | * @throws ParseException 250 | */ 251 | public static Date getDate8(Date date){ 252 | try { 253 | return parse8(format8(date)); 254 | } catch (ParseException e) { 255 | //unreachable 256 | } 257 | return null; 258 | } 259 | 260 | /** 261 | * 时间加days天 262 | * @param date 263 | * @param days 264 | * @return 265 | */ 266 | public static Date addDate(Date date, int days){ 267 | Calendar calendar = Calendar.getInstance(); 268 | calendar.setTime(date); 269 | calendar.add(Calendar.DATE, days); 270 | return calendar.getTime(); 271 | } 272 | 273 | /** 274 | * 日期加法运算 275 | * 276 | * @param sourceDate 277 | * @param months 增加的月数,可为负数 278 | * @return 279 | */ 280 | public static Date addMonths(Date sourceDate, int months) { 281 | Calendar calendar = Calendar.getInstance(); 282 | calendar.setTime(sourceDate); 283 | calendar.add(Calendar.MONTH, months); 284 | return calendar.getTime(); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/utils/ProjectMD5Utils.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.utils; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | /** 9 | *md5工具类 10 | */ 11 | @Component 12 | public class ProjectMD5Utils { 13 | private static final char[] hexDigits = {'0', '1', '2', '3', '4', 14 | '5', '6', '7', '8', '9', 15 | 'a', 'b', 'c', 'd', 'e', 'f'}; 16 | 17 | public final static String getMD5String(String s) { 18 | 19 | byte[] btInput = s.getBytes(); 20 | //获得MD5摘要算法的 MessageDigest 对象 21 | MessageDigest mdInst = null; 22 | try { 23 | mdInst = MessageDigest.getInstance("MD5"); 24 | } catch (NoSuchAlgorithmException e) { 25 | //unreachable 26 | } 27 | //使用指定的字节更新摘要 28 | mdInst.update(btInput); 29 | //获得密文 30 | byte[] md = mdInst.digest(); 31 | //把密文转换成十六进制的字符串形式 32 | int j = md.length; 33 | char str[] = new char[j * 2]; 34 | int k = 0; 35 | for (int i = 0; i < j; i++) { 36 | byte byte0 = md[i]; 37 | str[k++] = hexDigits[byte0 >>> 4 & 0xf]; 38 | str[k++] = hexDigits[byte0 & 0xf]; 39 | } 40 | return new String(str); 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/utils/ProjectPlatTraceUtils.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.utils; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | 8 | /** 9 | * 平台流水生成器(单机) 10 | * 11 | */ 12 | public class ProjectPlatTraceUtils { 13 | /** 14 | * 默认完整时间格式 yyyyMMddHHmmssS 17位 15 | */ 16 | public static String DEFAULT_FULL_DATE_FMT = "yyyyMMddHHmmssS"; 17 | 18 | private static final int LIMIT = 1000000; 19 | private static final int START = 1; 20 | private static int seqHouse = START; 21 | 22 | /** 23 | * 生成平台流水 24 | * 25 | * @param dateFormat 日期格式 26 | * @param busiFlag 业务标识 27 | * @param totalLength 流水字符串长度,若该长度小于dateFormat.length()+busiFlag.lenght(),则从左到右依次截取 28 | * 29 | * @return 30 | */ 31 | public static String genTrace(String dateFormat, String busiFlag, int totalLength) { 32 | if (StringUtils.isBlank(dateFormat)) { 33 | throw new IllegalArgumentException("Param dateFormat should not be null."); 34 | } 35 | if (totalLength < 1) { 36 | throw new IllegalArgumentException("Param total length should be greater than 0."); 37 | } 38 | if (StringUtils.isBlank(busiFlag)) { 39 | busiFlag = ""; 40 | } 41 | 42 | Date date = new Date(); 43 | SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); 44 | 45 | StringBuilder builder = new StringBuilder(sdf.format(date)).append(busiFlag); 46 | if (totalLength == builder.length()) { 47 | return builder.toString(); 48 | } 49 | 50 | if (totalLength < builder.length()) { 51 | return builder.substring(0, totalLength); 52 | } 53 | 54 | //拼接流水 55 | String currentSeq = ""; 56 | synchronized (ProjectPlatTraceUtils.class) { 57 | if (seqHouse >= LIMIT) { 58 | seqHouse = START; 59 | } 60 | currentSeq = "" + seqHouse; 61 | seqHouse++; 62 | } 63 | 64 | return builder.append(formatNum(currentSeq, totalLength - builder.length())).toString(); 65 | } 66 | 67 | 68 | /** 69 | * 生成平台流水 70 | * 71 | * @param dateFormat 日期格式 72 | * @param busiFlag 业务标识 73 | * @param seqSuffix 末尾流水号 74 | * @param totalLength 流水字符串长度,若该长度小于dateFormat.length()+busiFlag.lenght(),则从左到右依次截取 75 | * @param totalLength 76 | * @return 77 | */ 78 | public static String genTrace(String dateFormat, String busiFlag, String seqSuffix, int totalLength) { 79 | if (StringUtils.isBlank(dateFormat)) { 80 | throw new IllegalArgumentException("Param dateFormat should not be null."); 81 | } 82 | if (totalLength < 1) { 83 | throw new IllegalArgumentException("Param total length should be greater than 0."); 84 | } 85 | if (StringUtils.isBlank(busiFlag)) { 86 | busiFlag = ""; 87 | } 88 | 89 | Date date = new Date(); 90 | SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); 91 | 92 | StringBuilder builder = new StringBuilder(busiFlag).append(sdf.format(date)); 93 | if (totalLength == builder.length()) { 94 | return builder.toString(); 95 | } 96 | 97 | if (totalLength < builder.length()) { 98 | return builder.substring(0, totalLength); 99 | } 100 | 101 | 102 | return builder.append(formatNum(seqSuffix, totalLength - builder.length())).toString(); 103 | } 104 | 105 | 106 | /** 107 | * 生成平台流水 108 | * 109 | * @param dateFormat 日期格式 110 | * @param totalLength 流水字符串长度,若该长度小于dateFormat.length()+busiFlag.lenght(),则从左到右依次截取 111 | * 112 | * @return 113 | */ 114 | public static String genTrace(String dateFormat, int totalLength) { 115 | return genTrace(dateFormat, "", totalLength); 116 | } 117 | 118 | /** 119 | * 生成平台流水30位 含busiFlag长度 120 | * 121 | * @param busiFlag 业务标识 122 | * 123 | * @return 124 | */ 125 | public static String genTrace30(String busiFlag) { 126 | return genTrace(DEFAULT_FULL_DATE_FMT, busiFlag, 30); 127 | } 128 | 129 | /** 130 | * 生成平台流水27位 含busiFlag长度 131 | * 132 | * @param busiFlag 业务标识 133 | * 134 | * @return 135 | */ 136 | public static String genTrace27(String busiFlag) { 137 | return genTrace(DEFAULT_FULL_DATE_FMT, busiFlag, 27); 138 | } 139 | 140 | 141 | /** 142 | * 格式化数字到指定长度。若length>numStr.lenght(), 则前补0;否则从右到左截取 143 | * 144 | * @param numStr 145 | * @param length 146 | * 147 | * @return 148 | */ 149 | public static String formatNum(String numStr, int length) { 150 | if (numStr.length() == length) { 151 | return numStr; 152 | } 153 | if (numStr.length() > length) { 154 | return numStr.substring(numStr.length() - length, numStr.length()); 155 | } 156 | int delta = length - numStr.length(); 157 | 158 | StringBuilder builder = new StringBuilder(); 159 | for (int i = 0; i < delta; i++) { 160 | builder.append(0); 161 | } 162 | 163 | return builder.append(numStr).toString(); 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/core/utils/ProjectVerifyUtils.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.utils; 2 | 3 | import com.company.project.core.result.ResultCode; 4 | import com.company.project.core.exception.ServiceException; 5 | import org.apache.commons.lang.ArrayUtils; 6 | import org.apache.commons.lang.StringUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.text.ParseException; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Date; 13 | 14 | /** 15 | * 参数格式校验工具类 16 | * Created by Jimersy Lee 17 | */ 18 | public class ProjectVerifyUtils { 19 | private static final Logger logger = LoggerFactory.getLogger(ProjectVerifyUtils.class); 20 | 21 | /** 22 | * 正则表达式 数字+英文字母 23 | */ 24 | public static final String REGX_ALPHABETA_NUM = "^[a-zA-Z0-9]$"; 25 | 26 | /** 27 | * 中国大陆手机号 28 | */ 29 | public static final String REGX_CN_MOBILE = "^(0086|[+]86)?1\\d{10}"; 30 | 31 | /** 32 | * 邮编 33 | */ 34 | public static final String REGEX_POSTCODE = "^[0-9]{6}$"; 35 | 36 | /** 37 | * 用于校验 0, 1 38 | */ 39 | public static final String PEGEX_YES_OR_NO = "^[0-1]{1}$"; 40 | 41 | 42 | public static boolean verifyPhoneNo(String phoneNo, boolean throwException, String fieldName) throws ServiceException { 43 | notNull(phoneNo, fieldName, true); 44 | if (phoneNo.matches("^1[0-9]{10}")) { 45 | return true; 46 | } 47 | if (throwException) { 48 | throw new ServiceException(ResultCode.INVALID_PARAM, fieldName); 49 | } 50 | return false; 51 | } 52 | 53 | 54 | public static boolean notBlank(String value, boolean throwException) throws ServiceException { 55 | return notBlank(value, "", throwException); 56 | } 57 | 58 | /** 59 | * 非空白判断 60 | * 61 | * @param value 62 | * @param paramName 63 | * @param throwException 64 | * @return 65 | * @throws ServiceException 66 | */ 67 | public static boolean notBlank(String value, String paramName, boolean throwException) throws ServiceException { 68 | boolean result = StringUtils.isNotBlank(value); 69 | if (result) { 70 | return true; 71 | } 72 | if (throwException) { 73 | throw new ServiceException(ResultCode.INVALID_PARAM, paramName); 74 | } else { 75 | return false; 76 | } 77 | } 78 | 79 | public static boolean greaterThan(int value, int compareValue, String paramName, boolean throwException) 80 | throws ServiceException { 81 | boolean result = value > compareValue; 82 | if (result) { 83 | return true; 84 | } 85 | if (throwException) { 86 | throw new ServiceException(ResultCode.INVALID_PARAM, paramName + "=" + value); 87 | } 88 | return false; 89 | } 90 | 91 | public static boolean greaterThan(long value, long compareValue, String paramName, boolean throwException) 92 | throws ServiceException { 93 | boolean result = value > compareValue; 94 | if (result) { 95 | return true; 96 | } 97 | if (throwException) { 98 | throw new ServiceException(ResultCode.INVALID_PARAM, paramName + "=" + value); 99 | } 100 | return false; 101 | } 102 | 103 | public static boolean greaterThan(double value, double compareValue, String paramName, boolean throwException) 104 | throws ServiceException { 105 | boolean result = value > compareValue; 106 | if (result) { 107 | return true; 108 | } 109 | if (throwException) { 110 | throw new ServiceException(ResultCode.INVALID_PARAM, paramName + "=" + value); 111 | } 112 | return false; 113 | } 114 | 115 | public static boolean maxStringLength(String value, int maxLength, String paramName, boolean allowBlank, 116 | boolean throwException) throws ServiceException { 117 | //非空校验 118 | boolean isNotBlank = notBlank(value, false); 119 | if (!isNotBlank) { 120 | if (allowBlank) { 121 | return true; 122 | } 123 | if (throwException) { 124 | throw new ServiceException(paramName + "=" + value); 125 | } 126 | return false; 127 | } 128 | //字符串长度校验 129 | if (value.length() > maxLength) { 130 | if (throwException) { 131 | throw new ServiceException(paramName + "=" + value); 132 | } 133 | return false; 134 | } 135 | return true; 136 | } 137 | 138 | /** 139 | * 检查数据最小字符串长度 140 | * 141 | * @param value 142 | * @param minLength 143 | * @param paramName 144 | * @param allowBlank 145 | * @param throwException 146 | * @return 147 | * @throws ServiceException 148 | */ 149 | public static boolean minStringLength(String value, int minLength, String paramName, boolean allowBlank, 150 | boolean throwException) throws ServiceException { 151 | //非空校验 152 | if (!notBlank(value, false)) { 153 | if (allowBlank) { 154 | return true; 155 | } 156 | if (throwException) { 157 | throw new ServiceException(paramName + "=" + value); 158 | } 159 | return false; 160 | } 161 | 162 | //字符串长度校验 163 | if (value.length() < minLength) { 164 | if (throwException) { 165 | throw new ServiceException(paramName + "=" + value); 166 | } 167 | return false; 168 | } 169 | return true; 170 | } 171 | 172 | 173 | public static boolean contains(int value, int[] container, String paramName, boolean throwException) 174 | throws ServiceException { 175 | if (container == null) { 176 | logger.error("Param container is null"); 177 | throw new ServiceException("container"); 178 | } 179 | 180 | boolean result = ArrayUtils.contains(container, value); 181 | if (result) { 182 | return true; 183 | } 184 | if (throwException) { 185 | throw new ServiceException(paramName + "=" + value); 186 | } 187 | return false; 188 | } 189 | 190 | /** 191 | * 非空判断 192 | * 193 | * @param obj 194 | * @param throwException 195 | * @return 196 | * @throws ServiceException 197 | */ 198 | public static boolean notNull(Object obj, String paramName, boolean throwException) throws ServiceException { 199 | if (obj == null) { 200 | if (throwException) { 201 | throw new ServiceException(paramName + " is null"); 202 | } 203 | return false; 204 | } 205 | return true; 206 | } 207 | 208 | 209 | /** 210 | * 非空白判断 211 | * 212 | * @param field 213 | * @param throwException 214 | * @param fieldName 215 | * @return 216 | * @throws ServiceException 217 | */ 218 | public static boolean notBlank(String field, boolean throwException, String fieldName) throws ServiceException { 219 | if (StringUtils.isNotBlank(field)) { 220 | return true; 221 | } 222 | if (throwException) { 223 | throw new ServiceException(ResultCode.INVALID_PARAM, fieldName + " 不能为空白"); 224 | } 225 | return false; 226 | } 227 | 228 | /** 229 | * 根据正则表达式判断参数合法性 230 | * 231 | * @param field 232 | * @param regex 233 | * @param throwException 234 | * @param fieldName 235 | * @return 236 | * @throws ServiceException 237 | */ 238 | public static boolean verifyByRegex(String field, String regex, boolean throwException, String fieldName) 239 | throws ServiceException { 240 | notNull(field, fieldName, true); 241 | if (field.matches(regex)) { 242 | return true; 243 | } 244 | if (throwException) { 245 | // logger.warn(fieldName + " is not match regex[" + regex + "]"); 246 | throw new ServiceException(fieldName + "=" + field); 247 | } 248 | return false; 249 | } 250 | 251 | /** 252 | * 日期字符串校验 253 | * 254 | * @param value 255 | * @param format 256 | * @param fieldName 257 | * @param throwException 258 | * @return 259 | * @throws ServiceException 260 | */ 261 | public static boolean verifyDate(String value, String format, String fieldName, boolean throwException) 262 | throws ServiceException { 263 | boolean result = notNull(value, fieldName, throwException); 264 | if (!result) { 265 | return result; 266 | } 267 | SimpleDateFormat sdf = new SimpleDateFormat(format); 268 | Date date = null; 269 | try { 270 | date = sdf.parse(value); 271 | } catch (ParseException e) { 272 | if (throwException) { 273 | throw new ServiceException(ResultCode.INVALID_PARAM, fieldName + "=" + value); 274 | } else { 275 | return false; 276 | } 277 | } 278 | String actValue = sdf.format(date); 279 | if (StringUtils.equals(value, actValue)) { 280 | return true; 281 | } 282 | if (throwException) { 283 | // logger.warn(fieldName + " is not match regex[" + regex + "]"); 284 | throw new ServiceException(ResultCode.INVALID_PARAM, fieldName + "=" + value); 285 | } 286 | return false; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/dao/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.company.project.dao; 2 | 3 | import com.company.project.core.mapper.Mapper; 4 | import com.company.project.model.User; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public interface UserMapper extends Mapper { 9 | } -------------------------------------------------------------------------------- /src/main/java/com/company/project/model/BaseDO.java: -------------------------------------------------------------------------------- 1 | package com.company.project.model; 2 | 3 | import org.apache.commons.lang.builder.ToStringBuilder; 4 | import org.apache.commons.lang.builder.ToStringStyle; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * 基础DO类,提供toString快方法 10 | * Created by liwei on 2015/6/16. 11 | */ 12 | public class BaseDO implements Serializable { 13 | 14 | private static final long serialVersionUID = -1394589131426860408L; 15 | 16 | public String toString() { 17 | return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/model/User.java: -------------------------------------------------------------------------------- 1 | package com.company.project.model; 2 | 3 | import java.util.Date; 4 | import javax.persistence.*; 5 | 6 | public class User extends BaseDO{ 7 | @Id 8 | @GeneratedValue(strategy = GenerationType.IDENTITY) 9 | private Integer id; 10 | 11 | private String username; 12 | 13 | private String password; 14 | 15 | @Column(name = "nick_name") 16 | private String nickName; 17 | 18 | private Integer sex; 19 | 20 | @Column(name = "register_date") 21 | private Date registerDate; 22 | 23 | /** 24 | * @return id 25 | */ 26 | public Integer getId() { 27 | return id; 28 | } 29 | 30 | /** 31 | * @param id 32 | */ 33 | public void setId(Integer id) { 34 | this.id = id; 35 | } 36 | 37 | /** 38 | * @return username 39 | */ 40 | public String getUsername() { 41 | return username; 42 | } 43 | 44 | /** 45 | * @param username 46 | */ 47 | public void setUsername(String username) { 48 | this.username = username; 49 | } 50 | 51 | /** 52 | * @return password 53 | */ 54 | public String getPassword() { 55 | return password; 56 | } 57 | 58 | /** 59 | * @param password 60 | */ 61 | public void setPassword(String password) { 62 | this.password = password; 63 | } 64 | 65 | /** 66 | * @return nick_name 67 | */ 68 | public String getNickName() { 69 | return nickName; 70 | } 71 | 72 | /** 73 | * @param nickName 74 | */ 75 | public void setNickName(String nickName) { 76 | this.nickName = nickName; 77 | } 78 | 79 | /** 80 | * @return sex 81 | */ 82 | public Integer getSex() { 83 | return sex; 84 | } 85 | 86 | /** 87 | * @param sex 88 | */ 89 | public void setSex(Integer sex) { 90 | this.sex = sex; 91 | } 92 | 93 | /** 94 | * @return register_date 95 | */ 96 | public Date getRegisterDate() { 97 | return registerDate; 98 | } 99 | 100 | /** 101 | * @param registerDate 102 | */ 103 | public void setRegisterDate(Date registerDate) { 104 | this.registerDate = registerDate; 105 | } 106 | } -------------------------------------------------------------------------------- /src/main/java/com/company/project/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.company.project.service; 2 | import com.company.project.model.User; 3 | import com.company.project.core.service.Service; 4 | 5 | 6 | /** 7 | * Created by CodeGenerator on 2017/08/21. 8 | */ 9 | public interface UserService extends Service { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.company.project.service.impl; 2 | 3 | import com.company.project.dao.UserMapper; 4 | import com.company.project.model.User; 5 | import com.company.project.service.UserService; 6 | import com.company.project.core.service.AbstractService; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import javax.annotation.Resource; 11 | 12 | 13 | /** 14 | * Created by CodeGenerator on 2017/08/21. 15 | */ 16 | @Service 17 | @Transactional 18 | public class UserServiceImpl extends AbstractService implements UserService { 19 | @Resource 20 | private UserMapper userMapper; 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/company/project/web/UserController.java: -------------------------------------------------------------------------------- 1 | package com.company.project.web; 2 | import com.company.project.core.exception.ServiceException; 3 | import com.company.project.core.result.Result; 4 | import com.company.project.core.result.ResultCode; 5 | import com.company.project.core.result.ResultGenerator; 6 | import com.company.project.model.User; 7 | import com.company.project.service.UserService; 8 | import com.github.pagehelper.PageHelper; 9 | import com.github.pagehelper.PageInfo; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | import javax.annotation.Resource; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by CodeGenerator on 2017/08/21. 20 | */ 21 | @RestController 22 | @RequestMapping(value="/user",produces="application/json;charset=UTF-8") 23 | public class UserController { 24 | @Resource 25 | private UserService userService; 26 | 27 | @PostMapping("/add") 28 | public Result add(User user) { 29 | userService.save(user); 30 | return ResultGenerator.genSuccessResult(); 31 | } 32 | 33 | @PostMapping("/delete") 34 | public Result delete(@RequestParam Integer id) { 35 | userService.deleteById(id); 36 | return ResultGenerator.genSuccessResult(); 37 | } 38 | 39 | @PostMapping("/update") 40 | public Result update(User user) { 41 | userService.update(user); 42 | return ResultGenerator.genSuccessResult(); 43 | } 44 | 45 | @RequestMapping("/detail") 46 | public Result detail(@RequestParam Integer id) { 47 | User user = userService.findById(id); 48 | return ResultGenerator.genSuccessResult(user); 49 | } 50 | 51 | @RequestMapping("/list") 52 | public Result list(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "0") Integer size) { 53 | PageHelper.startPage(page, size); 54 | List list = userService.findAll(); 55 | PageInfo pageInfo = new PageInfo<>(list); 56 | return ResultGenerator.genSuccessResult(pageInfo); 57 | } 58 | 59 | @RequestMapping("/test") 60 | public Result test(){ 61 | return new Result(ResultCode.FAIL); 62 | } 63 | 64 | @RequestMapping("/e") 65 | public Result e() throws ServiceException { 66 | throw new ServiceException(ResultCode.INVALID_PARAM); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | # \u5F00\u53D1\u73AF\u5883\u914D\u7F6E 2 | # \u6570\u636E\u6E90\u914D\u7F6E\uFF0C\u8BF7\u4FEE\u6539\u4E3A\u4F60\u9879\u76EE\u7684\u5B9E\u9645\u914D\u7F6E 3 | 4 | #mysql数据库连接配置 5 | spring.datasource.url=jdbc:mysql://192.168.0.174:3306/test 6 | spring.datasource.username=user 7 | spring.datasource.password=password 8 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 9 | 10 | 11 | #rabbitMQ配置 12 | #服务器地址 13 | spring.rabbitmq.host=192.168.0.174 14 | #服务器端口 15 | spring.rabbitmq.port=5672 16 | #登录名,rabbitMQ默认账号为guest 17 | spring.rabbitmq.username=jimersylee 18 | #密码,默认密码为guest 19 | spring.rabbitmq.password=jimersylee 20 | 21 | 22 | #redis配置 23 | spring.redis.host=192.168.0.174 24 | spring.redis.port=6379 25 | spring.redis.password= 26 | spring.redis.database=1 27 | spring.redis.pool.max-active=8 28 | spring.redis.pool.max-wait=-1 29 | spring.redis.pool.max-idle=500 30 | spring.redis.pool.min-idle=0 31 | spring.redis.timeout=0 32 | 33 | #日志级别配置 34 | debug=false 35 | logging.level.root=INFO 36 | logging.level.org.springframework.web=INFO 37 | logging.path=/home/jimersylee/projects/java/spring-boot-api-project-seed/log/ 38 | 39 | 40 | 41 | ######################################################## 42 | ###FREEMARKER (FreeMarkerAutoConfiguration) 43 | ######################################################## 44 | #spring.freemarker.enabled=true 45 | #spring.freemarker.allow-request-override=false 46 | #spring.freemarker.cache=true 47 | #spring.freemarker.check-template-location=true 48 | #spring.freemarker.charset=UTF-8 49 | #spring.freemarker.content-type=text/html 50 | #spring.freemarker.expose-request-attributes=false 51 | #spring.freemarker.expose-session-attributes=false 52 | #spring.freemarker.expose-spring-macro-helpers=false 53 | #spring.freemarker.prefix= 54 | #spring.freemarker.request-context-attribute= 55 | #spring.freemarker.settings.*= 56 | #spring.freemarker.suffix=.ftl 57 | #comma-separated list 58 | #spring.freemarker.template-loader-path=classpath:/templates/ 59 | #spring.freemarker.view-names= # whitelist of view names that can be resolved 60 | 61 | 62 | #java mail配置,使用此配置发送邮件 63 | #spring.mail.host=smtp.163.com 64 | #spring.mail.username=请输入用户名 65 | #spring.mail.password=请输入密码 66 | #spring.mail.port=25 67 | #spring.mail.protocol=smtp 68 | #spring.mail.default-encoding=UTF-8 69 | 70 | 71 | 72 | #应用监控配置 73 | #访问敏感接口是否启动鉴权 74 | management.security.enabled=false 75 | 76 | 77 | #log4j配置 78 | #spring.datasource.druid.filter.log4j.connection-log-enabled=true 79 | 80 | 81 | 82 | ## Dubbo 服务提供者配置 83 | spring.dubbo.application.name=provider 84 | spring.dubbo.registry.address=zookeeper://192.168.0.174:2181 85 | spring.dubbo.protocol.name=dubbo 86 | spring.dubbo.protocol.port=20880 87 | spring.dubbo.scan=com.company.project.core.service.dubbo 88 | 89 | 90 | 91 | 92 | 93 | ##zookeeper配置 94 | ZK_HOSTS=192.168.0.174:2181 95 | EVENT_CONSUMER_NUM=2 96 | SESSION_TIMEOUT=3600000 97 | 98 | #将本机服务注册到zookeeper上的配置 99 | #本机ip地址,可以不填,自动获取 100 | #LOCAL_HOST_NAME=192.168.0.174 101 | #本机对外服务端口,默认8080 102 | LOCAL_PORT=8080 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/main/resources/application-pro.properties: -------------------------------------------------------------------------------- 1 | # \u751F\u4EA7\u73AF\u5883\u914D\u7F6E -------------------------------------------------------------------------------- /src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | # \u6D4B\u8BD5\u73AF\u5883\u914D\u7F6E -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=dev 2 | # \u6240\u6709\u73AF\u5883\u901A\u7528\u7684\u914D\u7F6E\uFF0C\u653E\u5728\u8FD9\u91CC 3 | 4 | # 404 \u4EA4\u7ED9\u5F02\u5E38\u5904\u7406\u5668\u5904\u7406 5 | spring.mvc.throw-exception-if-no-handler-found=true 6 | spring.resources.add-mappings=false -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////// 2 | // _ooOoo_ // 3 | // o8888888o // 4 | // 88" . "88 // 5 | // (| ^_^ |) // 6 | // O\ = /O // 7 | // ____/`---'\____ // 8 | // .' \\| |// `. // 9 | // / \\||| : |||// \ // 10 | // / _||||| -:- |||||- \ // 11 | // | | \\\ - /// | | // 12 | // | \_| ''\---/'' | | // 13 | // \ .-\__ `-` ___/-. / // 14 | // ___`. .' /--.--\ `. . ___ // 15 | // ."" '< `.___\_<|>_/___.' >'"". // 16 | // | | : `- \`.;`\ _ /`;.`/ - ` : | | // 17 | // \ \ `-. \_ __\ /__ _/ .-` / / // 18 | // ========`-.____`-.___\_____/___.-`____.-'======== // 19 | // `=---=' // 20 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // 21 | // 佛祖保佑 永不宕机 永无BUG // 22 | //////////////////////////////////////////////////////////////////// -------------------------------------------------------------------------------- /src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/templates/welcome.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Date: ${time?date}
5 | Message: ${message} 6 | 7 | -------------------------------------------------------------------------------- /src/test/java/CodeGenerator.java: -------------------------------------------------------------------------------- 1 | import com.google.common.base.CaseFormat; 2 | import freemarker.template.TemplateExceptionHandler; 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.mybatis.generator.api.MyBatisGenerator; 5 | import org.mybatis.generator.config.*; 6 | import org.mybatis.generator.internal.DefaultShellCallback; 7 | 8 | import java.io.File; 9 | import java.io.FileWriter; 10 | import java.io.IOException; 11 | import java.text.SimpleDateFormat; 12 | import java.util.*; 13 | 14 | import static com.company.project.core.constant.ProjectConstant.*; 15 | 16 | /** 17 | * 代码生成器,根据数据表名称生成对应的Model、Mapper、Service、Controller简化开发。 18 | */ 19 | public class CodeGenerator { 20 | //JDBC配置,请修改为你项目的实际配置 21 | private static final String JDBC_URL = "jdbc:mysql://localhost:3306/test"; 22 | private static final String JDBC_USERNAME = "root"; 23 | private static final String JDBC_PASSWORD = ""; 24 | private static final String JDBC_DIVER_CLASS_NAME = "com.mysql.jdbc.Driver"; 25 | 26 | private static final String PROJECT_PATH = System.getProperty("user.dir");//项目在硬盘上的基础路径 27 | private static final String TEMPLATE_FILE_PATH = PROJECT_PATH + "/src/test/resources/generator/template";//模板位置 28 | 29 | private static final String JAVA_PATH = "/src/main/java"; //java文件路径 30 | private static final String RESOURCES_PATH = "/src/main/resources";//资源文件路径 31 | 32 | private static final String PACKAGE_PATH_SERVICE = packageConvertPath(SERVICE_PACKAGE);//生成的Service存放路径 33 | private static final String PACKAGE_PATH_SERVICE_IMPL = packageConvertPath(SERVICE_IMPL_PACKAGE);//生成的Service实现存放路径 34 | private static final String PACKAGE_PATH_CONTROLLER = packageConvertPath(CONTROLLER_PACKAGE);//生成的Controller存放路径 35 | 36 | private static final String AUTHOR = "CodeGenerator";//@author 37 | private static final String DATE = new SimpleDateFormat("yyyy/MM/dd").format(new Date());//@date 38 | 39 | public static void main(String[] args) { 40 | genCode("trans_const_deal_order"); 41 | //genCode("输入表名","输入自定义Model名称"); 42 | } 43 | 44 | /** 45 | * 通过数据表名称生成代码,Model 名称通过解析数据表名称获得,下划线转大驼峰的形式。 46 | * 如输入表名称 "t_user_detail" 将生成 TUserDetail、TUserDetailMapper、TUserDetailService ... 47 | * @param tableNames 数据表名称... 48 | */ 49 | public static void genCode(String... tableNames) { 50 | for (String tableName : tableNames) { 51 | genCode(tableName, null); 52 | } 53 | } 54 | 55 | /** 56 | * 通过数据表名称,和自定义的 Model 名称生成代码 57 | * 如输入表名称 "t_user_detail" 和自定义的 Model 名称 "User" 将生成 User、UserMapper、UserService ... 58 | * @param tableName 数据表名称 59 | * @param modelName 自定义的 Model 名称 60 | */ 61 | public static void genCode(String tableName, String modelName) { 62 | genModelAndMapper(tableName, modelName); 63 | genService(tableName, modelName); 64 | genController(tableName, modelName); 65 | } 66 | 67 | 68 | public static void genModelAndMapper(String tableName, String modelName) { 69 | Context context = new Context(ModelType.FLAT); 70 | context.setId("Potato"); 71 | context.setTargetRuntime("MyBatis3Simple"); 72 | context.addProperty(PropertyRegistry.CONTEXT_BEGINNING_DELIMITER, "`"); 73 | context.addProperty(PropertyRegistry.CONTEXT_ENDING_DELIMITER, "`"); 74 | 75 | JDBCConnectionConfiguration jdbcConnectionConfiguration = new JDBCConnectionConfiguration(); 76 | jdbcConnectionConfiguration.setConnectionURL(JDBC_URL); 77 | jdbcConnectionConfiguration.setUserId(JDBC_USERNAME); 78 | jdbcConnectionConfiguration.setPassword(JDBC_PASSWORD); 79 | jdbcConnectionConfiguration.setDriverClass(JDBC_DIVER_CLASS_NAME); 80 | context.setJdbcConnectionConfiguration(jdbcConnectionConfiguration); 81 | 82 | PluginConfiguration pluginConfiguration = new PluginConfiguration(); 83 | pluginConfiguration.setConfigurationType("tk.mybatis.mapper.generator.MapperPlugin"); 84 | pluginConfiguration.addProperty("mappers", MAPPER_INTERFACE_REFERENCE); 85 | context.addPluginConfiguration(pluginConfiguration); 86 | 87 | JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = new JavaModelGeneratorConfiguration(); 88 | javaModelGeneratorConfiguration.setTargetProject(PROJECT_PATH + JAVA_PATH); 89 | javaModelGeneratorConfiguration.setTargetPackage(MODEL_PACKAGE); 90 | context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfiguration); 91 | 92 | SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration = new SqlMapGeneratorConfiguration(); 93 | sqlMapGeneratorConfiguration.setTargetProject(PROJECT_PATH + RESOURCES_PATH); 94 | sqlMapGeneratorConfiguration.setTargetPackage("mapper"); 95 | context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfiguration); 96 | 97 | JavaClientGeneratorConfiguration javaClientGeneratorConfiguration = new JavaClientGeneratorConfiguration(); 98 | javaClientGeneratorConfiguration.setTargetProject(PROJECT_PATH + JAVA_PATH); 99 | javaClientGeneratorConfiguration.setTargetPackage(MAPPER_PACKAGE); 100 | javaClientGeneratorConfiguration.setConfigurationType("XMLMAPPER"); 101 | context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfiguration); 102 | 103 | TableConfiguration tableConfiguration = new TableConfiguration(context); 104 | tableConfiguration.setTableName(tableName); 105 | tableConfiguration.setDomainObjectName(modelName); 106 | tableConfiguration.setGeneratedKey(new GeneratedKey("id", "Mysql", true, null)); 107 | context.addTableConfiguration(tableConfiguration); 108 | 109 | List warnings; 110 | MyBatisGenerator generator; 111 | try { 112 | Configuration config = new Configuration(); 113 | config.addContext(context); 114 | config.validate(); 115 | 116 | boolean overwrite = true; 117 | DefaultShellCallback callback = new DefaultShellCallback(overwrite); 118 | warnings = new ArrayList(); 119 | generator = new MyBatisGenerator(config, callback, warnings); 120 | generator.generate(null); 121 | } catch (Exception e) { 122 | throw new RuntimeException("生成Model和Mapper失败", e); 123 | } 124 | 125 | if (generator.getGeneratedJavaFiles().isEmpty() || generator.getGeneratedXmlFiles().isEmpty()) { 126 | throw new RuntimeException("生成Model和Mapper失败:" + warnings); 127 | } 128 | if (StringUtils.isEmpty(modelName)) modelName = tableNameConvertUpperCamel(tableName); 129 | System.out.println(modelName + ".java 生成成功"); 130 | System.out.println(modelName + "Mapper.java 生成成功"); 131 | System.out.println(modelName + "Mapper.xml 生成成功"); 132 | } 133 | 134 | public static void genService(String tableName, String modelName) { 135 | try { 136 | freemarker.template.Configuration cfg = getConfiguration(); 137 | 138 | Map data = new HashMap<>(); 139 | data.put("date", DATE); 140 | data.put("author", AUTHOR); 141 | String modelNameUpperCamel = StringUtils.isEmpty(modelName) ? tableNameConvertUpperCamel(tableName) : modelName; 142 | data.put("modelNameUpperCamel", modelNameUpperCamel); 143 | data.put("modelNameLowerCamel", tableNameConvertLowerCamel(tableName)); 144 | data.put("basePackage", BASE_PACKAGE); 145 | 146 | File file = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_PATH_SERVICE + modelNameUpperCamel + "Service.java"); 147 | if (!file.getParentFile().exists()) { 148 | file.getParentFile().mkdirs(); 149 | } 150 | cfg.getTemplate("service.ftl").process(data, 151 | new FileWriter(file)); 152 | System.out.println(modelNameUpperCamel + "Service.java 生成成功"); 153 | 154 | File file1 = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_PATH_SERVICE_IMPL + modelNameUpperCamel + "ServiceImpl.java"); 155 | if (!file1.getParentFile().exists()) { 156 | file1.getParentFile().mkdirs(); 157 | } 158 | cfg.getTemplate("service-impl.ftl").process(data, 159 | new FileWriter(file1)); 160 | System.out.println(modelNameUpperCamel + "ServiceImpl.java 生成成功"); 161 | } catch (Exception e) { 162 | throw new RuntimeException("生成Service失败", e); 163 | } 164 | } 165 | 166 | public static void genController(String tableName, String modelName) { 167 | try { 168 | freemarker.template.Configuration cfg = getConfiguration(); 169 | 170 | Map data = new HashMap<>(); 171 | data.put("date", DATE); 172 | data.put("author", AUTHOR); 173 | String modelNameUpperCamel = StringUtils.isEmpty(modelName) ? tableNameConvertUpperCamel(tableName) : modelName; 174 | data.put("baseRequestMapping", modelNameConvertMappingPath(modelNameUpperCamel)); 175 | data.put("modelNameUpperCamel", modelNameUpperCamel); 176 | data.put("modelNameLowerCamel", CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, modelNameUpperCamel)); 177 | data.put("basePackage", BASE_PACKAGE); 178 | 179 | File file = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_PATH_CONTROLLER + modelNameUpperCamel + "Controller.java"); 180 | if (!file.getParentFile().exists()) { 181 | file.getParentFile().mkdirs(); 182 | } 183 | //cfg.getTemplate("controller-restful.ftl").process(data, new FileWriter(file)); 184 | cfg.getTemplate("controller.ftl").process(data, new FileWriter(file)); 185 | 186 | System.out.println(modelNameUpperCamel + "Controller.java 生成成功"); 187 | } catch (Exception e) { 188 | throw new RuntimeException("生成Controller失败", e); 189 | } 190 | 191 | } 192 | 193 | private static freemarker.template.Configuration getConfiguration() throws IOException { 194 | freemarker.template.Configuration cfg = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_23); 195 | cfg.setDirectoryForTemplateLoading(new File(TEMPLATE_FILE_PATH)); 196 | cfg.setDefaultEncoding("UTF-8"); 197 | cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER); 198 | return cfg; 199 | } 200 | 201 | private static String tableNameConvertLowerCamel(String tableName) { 202 | return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, tableName.toLowerCase()); 203 | } 204 | 205 | private static String tableNameConvertUpperCamel(String tableName) { 206 | return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName.toLowerCase()); 207 | 208 | } 209 | 210 | private static String tableNameConvertMappingPath(String tableName) { 211 | tableName = tableName.toLowerCase();//兼容使用大写的表名 212 | return "/" + (tableName.contains("_") ? tableName.replaceAll("_", "/") : tableName); 213 | } 214 | 215 | private static String modelNameConvertMappingPath(String modelName) { 216 | String tableName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, modelName); 217 | return tableNameConvertMappingPath(tableName); 218 | } 219 | 220 | private static String packageConvertPath(String packageName) { 221 | return String.format("/%s/", packageName.contains(".") ? packageName.replaceAll("\\.", "/") : packageName); 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /src/test/java/com/company/project/Tester.java: -------------------------------------------------------------------------------- 1 | package com.company.project; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | /** 9 | * 单元测试继承该类即可 10 | */ 11 | @RunWith(SpringRunner.class) 12 | @SpringBootTest(classes = Application.class) 13 | @ConfigurationProperties 14 | public abstract class Tester { 15 | } 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/java/com/company/project/amqp/amqpTest.java: -------------------------------------------------------------------------------- 1 | package com.company.project.amqp; 2 | 3 | import com.company.project.core.service.amqp.Sender; 4 | import com.company.project.Tester; 5 | import org.junit.Test; 6 | import javax.annotation.Resource; 7 | 8 | public class amqpTest extends Tester { 9 | @Resource 10 | private Sender sender; 11 | @Test 12 | public void test(){ 13 | sender.send(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/company/project/async/asyncNeedResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.company.project.async; 2 | 3 | import com.company.project.Tester; 4 | import com.company.project.core.service.AsyncService.AsyncNeedResponseService; 5 | import org.junit.Test; 6 | 7 | import javax.annotation.Resource; 8 | import java.util.concurrent.Future; 9 | 10 | public class asyncNeedResponseTest extends Tester{ 11 | 12 | @Resource 13 | private AsyncNeedResponseService asyncNeedResponseService; 14 | 15 | 16 | @Test 17 | public void test() throws Exception{ 18 | long startTime=System.currentTimeMillis(); 19 | 20 | Future task1=asyncNeedResponseService.sendA(); 21 | Future task2=asyncNeedResponseService.sendB(); 22 | 23 | while(true){ 24 | if(task1.isDone() && task2.isDone()){ 25 | break; 26 | } 27 | } 28 | 29 | long endTime=System.currentTimeMillis(); 30 | System.out.println("总耗时:"+(endTime-startTime)); 31 | 32 | /*输出 33 | send B 34 | send A 35 | 耗时:2000 36 | 耗时:2000 37 | 总耗时:2019 38 | * */ 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/company/project/async/asyncNoResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.company.project.async; 2 | 3 | import com.company.project.Tester; 4 | import com.company.project.core.service.AsyncService.AsyncNoResponseService; 5 | import org.junit.Test; 6 | 7 | import javax.annotation.Resource; 8 | import java.io.IOException; 9 | 10 | public class asyncNoResponseTest extends Tester { 11 | @Resource 12 | private AsyncNoResponseService asyncNoResponseService; 13 | @Test 14 | public void test() throws InterruptedException, IOException { 15 | asyncNoResponseService.sendA(); 16 | asyncNoResponseService.sendB(); 17 | System.in.read();//防止程序结束 18 | 19 | 20 | /* 21 | 输出 22 | send A============================ 23 | send B============================ 24 | B 耗时:2000 25 | A 耗时:5000 26 | * */ 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/company/project/core/service/dubbo/City.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.dubbo; 2 | 3 | import com.company.project.model.BaseDO; 4 | 5 | 6 | 7 | /** 8 | * 城市实体类 9 | * 10 | * Created by bysocket on 07/02/2017. 11 | */ 12 | public class City extends BaseDO { 13 | 14 | 15 | /** 16 | * 城市编号 17 | */ 18 | private Long id; 19 | 20 | /** 21 | * 省份编号 22 | */ 23 | private Long provinceId; 24 | 25 | /** 26 | * 城市名称 27 | */ 28 | private String cityName; 29 | 30 | /** 31 | * 描述 32 | */ 33 | private String description; 34 | 35 | public City() { 36 | } 37 | 38 | public City(Long id, Long provinceId, String cityName, String description) { 39 | this.id = id; 40 | this.provinceId = provinceId; 41 | this.cityName = cityName; 42 | this.description = description; 43 | } 44 | 45 | public Long getId() { 46 | return id; 47 | } 48 | 49 | public void setId(Long id) { 50 | this.id = id; 51 | } 52 | 53 | public Long getProvinceId() { 54 | return provinceId; 55 | } 56 | 57 | public void setProvinceId(Long provinceId) { 58 | this.provinceId = provinceId; 59 | } 60 | 61 | public String getCityName() { 62 | return cityName; 63 | } 64 | 65 | public void setCityName(String cityName) { 66 | this.cityName = cityName; 67 | } 68 | 69 | public String getDescription() { 70 | return description; 71 | } 72 | 73 | public void setDescription(String description) { 74 | this.description = description; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/company/project/core/service/dubbo/CityDubboService.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.dubbo; 2 | 3 | 4 | /** 5 | * 必须保证此接口所在的包与provider一致 6 | * 城市业务 Dubbo 服务层 7 | * 8 | * Created by bysocket on 28/02/2017. 9 | */ 10 | public interface CityDubboService { 11 | 12 | /** 13 | * 根据城市名称,查询城市信息 14 | * @param cityName 15 | */ 16 | City findCityByName(String cityName); 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/company/project/core/service/dubbo/DubboConsumerTest.java: -------------------------------------------------------------------------------- 1 | package com.company.project.core.service.dubbo; 2 | 3 | import com.alibaba.dubbo.config.annotation.Reference; 4 | 5 | 6 | import com.company.project.Tester; 7 | import org.junit.Test; 8 | 9 | 10 | /** 11 | * 12 | */ 13 | public class DubboConsumerTest extends Tester{ 14 | 15 | 16 | @Reference(version = "1.0.0") 17 | CityDubboService consumerService; 18 | 19 | @Test 20 | public void test(){ 21 | City result=consumerService.findCityByName("杭州"); 22 | System.out.println("==================================="+result.toString()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/company/project/mail/code.txt: -------------------------------------------------------------------------------- 1 | package com.conpany.project.mail; 2 | 3 | import Tester; 4 | import org.junit.Test; 5 | import org.springframework.mail.SimpleMailMessage; 6 | import org.springframework.mail.javamail.JavaMailSender; 7 | import org.springframework.mail.javamail.MimeMessageHelper; 8 | 9 | import javax.annotation.Resource; 10 | import javax.mail.MessagingException; 11 | import javax.mail.internet.MimeMessage; 12 | 13 | 14 | 15 | public class mailTest extends Tester { 16 | @Resource 17 | private JavaMailSender mailSender; 18 | 19 | /** 20 | * 发送包含简单文本的邮件 21 | */ 22 | @Test 23 | public void sendTxtMail() { 24 | SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); 25 | //设置收件人,寄件人 26 | simpleMailMessage.setTo(new String[]{"jimersylee@gmail.com", "305008389@qq.com"}); 27 | simpleMailMessage.setFrom("305008389@qq.com"); 28 | simpleMailMessage.setSubject("Spring Boot Mail 邮件标题"); 29 | simpleMailMessage.setText("这里是一段简单文本"); 30 | //发送 31 | mailSender.send(simpleMailMessage); 32 | System.out.println("++++++++++++++++++++++邮件已发送"); 33 | } 34 | 35 | /** 36 | * 发送包含html的邮件 37 | */ 38 | @Test 39 | public void sendHtmlMail() throws MessagingException { 40 | MimeMessage mimeMessage = mailSender.createMimeMessage(); 41 | MimeMessageHelper mimeMessageHelper=new MimeMessageHelper(mimeMessage); 42 | mimeMessageHelper.setTo("305008389@qq.com"); 43 | mimeMessageHelper.setFrom("305008389@qq.com"); 44 | mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试[html]"); 45 | StringBuilder stringBuilder=new StringBuilder(); 46 | stringBuilder.append(""); 47 | stringBuilder.append("

spring 邮件测试

hello!this is spring mail test。

"); 48 | stringBuilder.append(""); 49 | 50 | //启用html 51 | mimeMessageHelper.setText(stringBuilder.toString(),true); 52 | //发送邮件 53 | mailSender.send(mimeMessage); 54 | System.out.println("++++++++++++++++++++++邮件已发送"); 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/company/project/redis/redisTest.java: -------------------------------------------------------------------------------- 1 | package com.company.project.redis; 2 | 3 | import com.company.project.Tester; 4 | import com.company.project.core.service.redis.RedisService; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | import javax.annotation.Resource; 9 | 10 | public class redisTest extends Tester { 11 | @Resource 12 | private RedisService redisService; 13 | 14 | private static final String KEY = "KEY"; 15 | private static final String VALUE = "VALUE"; 16 | 17 | @Test 18 | public void set() { 19 | String result = redisService.set(KEY, VALUE); 20 | Assert.assertEquals("OK", result); 21 | } 22 | 23 | @Test 24 | public void get() { 25 | redisService.set(KEY, VALUE); 26 | String result; 27 | result = redisService.get(KEY); 28 | Assert.assertEquals(VALUE, result); 29 | } 30 | 31 | @Test 32 | public void del() { 33 | 34 | long effectCount = redisService.delete(KEY); 35 | Assert.assertEquals(1, effectCount); 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | spring.dubbo.application.name=consumer 2 | spring.dubbo.registry.address=zookeeper://192.168.0.174:2181 3 | spring.dubbo.protocol.name=dubbo 4 | spring.dubbo.protocol.port=20881 5 | spring.dubbo.scan=com.company.project.core.service.dubbo 6 | 7 | 8 | ##zookeeper配置 9 | ZK_HOSTS=192.168.0.174:2181 10 | EVENT_CONSUMER_NUM=2 11 | SESSION_TIMEOUT=3600000 12 | 13 | #将本机服务注册到zookeeper上的配置 14 | #本机ip地址,可以不填,自动获取 15 | #LOCAL_HOST_NAME=192.168.0.174 16 | #本机对外服务端口,默认8080 17 | LOCAL_PORT=8080 -------------------------------------------------------------------------------- /src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=dev 2 | # \u6240\u6709\u73AF\u5883\u901A\u7528\u7684\u914D\u7F6E\uFF0C\u653E\u5728\u8FD9\u91CC 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/demo-user.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : Localhost 5 | Source Server Version : 50713 6 | Source Host : localhost:3306 7 | Source Database : test 8 | 9 | Target Server Type : MYSQL 10 | Target Server Version : 50713 11 | File Encoding : 65001 12 | 13 | Date: 2017-06-23 14:25:27 14 | */ 15 | 16 | SET FOREIGN_KEY_CHECKS=0; 17 | 18 | -- ---------------------------- 19 | -- Table structure for user 20 | -- ---------------------------- 21 | DROP TABLE IF EXISTS `user`; 22 | CREATE TABLE `user` ( 23 | `id` int(11) NOT NULL AUTO_INCREMENT, 24 | `username` varchar(255) NOT NULL, 25 | `password` varchar(255) NOT NULL, 26 | `nick_name` varchar(255) DEFAULT NULL, 27 | `sex` int(1) DEFAULT NULL, 28 | `register_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, 29 | PRIMARY KEY (`id`) 30 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 31 | 32 | -- ---------------------------- 33 | -- Records of user 34 | -- ---------------------------- 35 | INSERT INTO `user` VALUES ('1', '89921218@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆', '1', '2017-06-23 14:24:23'); 36 | INSERT INTO `user` VALUES ('2', '2@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆-2', '1', '2017-06-23 14:24:23'); 37 | INSERT INTO `user` VALUES ('3', '3@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆-3', '1', '2017-06-23 14:24:23'); 38 | INSERT INTO `user` VALUES ('4', '4@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆-4', '1', '2017-06-23 14:24:23'); 39 | INSERT INTO `user` VALUES ('5', '5@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆-5', '1', '2017-06-23 14:24:23'); 40 | INSERT INTO `user` VALUES ('6', '6@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆-6', '1', '2017-06-23 14:24:23'); 41 | INSERT INTO `user` VALUES ('7', '7@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆-7', '1', '2017-06-23 14:24:23'); 42 | INSERT INTO `user` VALUES ('8', '8@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆-8', '1', '2017-06-23 14:24:23'); 43 | INSERT INTO `user` VALUES ('9', '9@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆-9', '1', '2017-06-23 14:24:23'); 44 | INSERT INTO `user` VALUES ('10', '10@qq.com', '1ee04e0b1cb5af7367c80c22e42efd8b', '土豆-10', '1', '2017-06-23 14:24:23'); 45 | SET FOREIGN_KEY_CHECKS=1; 46 | -------------------------------------------------------------------------------- /src/test/resources/generator/template/controller-restful.ftl: -------------------------------------------------------------------------------- 1 | package ${basePackage}.web; 2 | 3 | import ${basePackage}.core.Result; 4 | import ${basePackage}.core.ResultGenerator; 5 | import ${basePackage}.model.${modelNameUpperCamel}; 6 | import ${basePackage}.service.${modelNameUpperCamel}Service; 7 | import com.github.pagehelper.PageHelper; 8 | import com.github.pagehelper.PageInfo; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by ${author} on ${date}. 16 | */ 17 | @RestController 18 | @RequestMapping("${baseRequestMapping}") 19 | public class ${modelNameUpperCamel}Controller { 20 | @Resource 21 | private ${modelNameUpperCamel}Service ${modelNameLowerCamel}Service; 22 | 23 | @PostMapping 24 | public Result add(@RequestBody ${modelNameUpperCamel} ${modelNameLowerCamel}) { 25 | ${modelNameLowerCamel}Service.save(${modelNameLowerCamel}); 26 | return ResultGenerator.genSuccessResult(); 27 | } 28 | 29 | @DeleteMapping("/{id}") 30 | public Result delete(@PathVariable Integer id) { 31 | ${modelNameLowerCamel}Service.deleteById(id); 32 | return ResultGenerator.genSuccessResult(); 33 | } 34 | 35 | @PutMapping 36 | public Result update(@RequestBody ${modelNameUpperCamel} ${modelNameLowerCamel}) { 37 | ${modelNameLowerCamel}Service.update(${modelNameLowerCamel}); 38 | return ResultGenerator.genSuccessResult(); 39 | } 40 | 41 | @GetMapping("/{id}") 42 | public Result detail(@PathVariable Integer id) { 43 | ${modelNameUpperCamel} ${modelNameLowerCamel} = ${modelNameLowerCamel}Service.findById(id); 44 | return ResultGenerator.genSuccessResult(${modelNameLowerCamel}); 45 | } 46 | 47 | @GetMapping 48 | public Result list(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "0") Integer size) { 49 | PageHelper.startPage(page, size); 50 | List<${modelNameUpperCamel}> list = ${modelNameLowerCamel}Service.findAll(); 51 | PageInfo pageInfo = new PageInfo(list); 52 | return ResultGenerator.genSuccessResult(pageInfo); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/resources/generator/template/controller.ftl: -------------------------------------------------------------------------------- 1 | package ${basePackage}.web; 2 | import ${basePackage}.core.Result; 3 | import ${basePackage}.core.ResultGenerator; 4 | import ${basePackage}.model.${modelNameUpperCamel}; 5 | import ${basePackage}.service.${modelNameUpperCamel}Service; 6 | import com.github.pagehelper.PageHelper; 7 | import com.github.pagehelper.PageInfo; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import javax.annotation.Resource; 14 | import java.util.List; 15 | 16 | /** 17 | * Created by ${author} on ${date}. 18 | */ 19 | @RestController 20 | @RequestMapping("${baseRequestMapping}") 21 | public class ${modelNameUpperCamel}Controller { 22 | @Resource 23 | private ${modelNameUpperCamel}Service ${modelNameLowerCamel}Service; 24 | 25 | @PostMapping("/add") 26 | public Result add(${modelNameUpperCamel} ${modelNameLowerCamel}) { 27 | ${modelNameLowerCamel}Service.save(${modelNameLowerCamel}); 28 | return ResultGenerator.genSuccessResult(); 29 | } 30 | 31 | @PostMapping("/delete") 32 | public Result delete(@RequestParam Integer id) { 33 | ${modelNameLowerCamel}Service.deleteById(id); 34 | return ResultGenerator.genSuccessResult(); 35 | } 36 | 37 | @PostMapping("/update") 38 | public Result update(${modelNameUpperCamel} ${modelNameLowerCamel}) { 39 | ${modelNameLowerCamel}Service.update(${modelNameLowerCamel}); 40 | return ResultGenerator.genSuccessResult(); 41 | } 42 | 43 | @RequestMapping("/detail") 44 | public Result detail(@RequestParam Integer id) { 45 | ${modelNameUpperCamel} ${modelNameLowerCamel} = ${modelNameLowerCamel}Service.findById(id); 46 | return ResultGenerator.genSuccessResult(${modelNameLowerCamel}); 47 | } 48 | 49 | @RequestMapping("/list") 50 | public Result list(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "0") Integer size) { 51 | PageHelper.startPage(page, size); 52 | List<${modelNameUpperCamel}> list = ${modelNameLowerCamel}Service.findAll(); 53 | PageInfo pageInfo = new PageInfo(list); 54 | return ResultGenerator.genSuccessResult(pageInfo); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/resources/generator/template/service-impl.ftl: -------------------------------------------------------------------------------- 1 | package ${basePackage}.service.impl; 2 | 3 | import ${basePackage}.dao.${modelNameUpperCamel}Mapper; 4 | import ${basePackage}.model.${modelNameUpperCamel}; 5 | import ${basePackage}.service.${modelNameUpperCamel}Service; 6 | import ${basePackage}.core.AbstractService; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import javax.annotation.Resource; 11 | 12 | 13 | /** 14 | * Created by ${author} on ${date}. 15 | */ 16 | @Service 17 | @Transactional 18 | public class ${modelNameUpperCamel}ServiceImpl extends AbstractService<${modelNameUpperCamel}> implements ${modelNameUpperCamel}Service { 19 | @Resource 20 | private ${modelNameUpperCamel}Mapper ${modelNameLowerCamel}Mapper; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/generator/template/service.ftl: -------------------------------------------------------------------------------- 1 | package ${basePackage}.service; 2 | import ${basePackage}.model.${modelNameUpperCamel}; 3 | import ${basePackage}.core.Service; 4 | 5 | 6 | /** 7 | * Created by ${author} on ${date}. 8 | */ 9 | public interface ${modelNameUpperCamel}Service extends Service<${modelNameUpperCamel}> { 10 | 11 | } 12 | --------------------------------------------------------------------------------