diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/.DS_Store differ diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..995fadeb --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # [vector4wang] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['https://raw.githubusercontent.com/vector4wang/spring-boot-quick/master/images/qiafan.png'] diff --git a/.github/workflows/github2gitee.yml b/.github/workflows/github2gitee.yml new file mode 100644 index 00000000..7d33fe01 --- /dev/null +++ b/.github/workflows/github2gitee.yml @@ -0,0 +1,17 @@ +# 暂停自动同步,后续手动同步 +# on: push +# name: Mirror GitHub Auto Queried Repos to Gitee +# jobs: +# run: +# name: Sync-GitHub-to-Gitee +# runs-on: ubuntu-latest +# steps: +# - name: Mirror the Github repos to Gitee. +# uses: Yikun/hub-mirror-action@master +# with: +# src: github/vector4wang +# dst: gitee/backwxc +# dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} +# dst_token: ${{ secrets.GITEE_TOKEN }} +# white_list: 'spring-boot-quick' +# force_update: true diff --git a/.gitignore b/.gitignore index 91ccbab5..657512c1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.jar *.war *.ear +*.zip # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* @@ -35,4 +36,7 @@ builds/ nbbuild/ dist/ nbdist/ -.nb-gradle/ \ No newline at end of file +.nb-gradle/ + +log/ +logs/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..875d995d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: java + +jdk: + - oraclejdk9 + +install: true + +script: test -B \ No newline at end of file diff --git a/README.md b/README.md index 067a4d86..92f505dd 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,82 @@ +## 2026年03月09日更新 +已经进入‌Vibe Coding,大家要拥抱AI哈~ + +--- + +## 2025年3月7日更新 +### 兄弟们,转go了,现在工作完全用go了,这个仓库就不再维护啦~~~ + +--- + +欢迎大家留言和PR~ + +> Tip: 技术更新换代太快,本仓库仅做参考,自己的项目具体使用哪个版本还需谨慎思考~(不推荐使用最新的版本,推荐使用(最新-1|2)的版本,会比较稳定) + +# spring-boot-quick +[![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) +[![知识共享协议(CC协议)](https://img.shields.io/badge/License-Creative%20Commons-DC3D24.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh) +[![Build Status](https://travis-ci.org/vector4wang/spring-boot-quick.svg?branch=master)](https://travis-ci.org/vector4wang/spring-boot-quick) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/vector4wang/spring-boot-quick.svg)](http://isitmaintained.com/project/vector4wang/spring-boot-quick "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/vector4wang/spring-boot-quick.svg)](http://isitmaintained.com/project/vector4wang/spring-boot-quick "Percentage of issues still open") +[![GitHub stars](https://img.shields.io/github/stars/vector4wang/spring-boot-quick.svg?style=flat&label=Star)](https://github.com/vector4wang/spring-boot-quick/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/vector4wang/spring-boot-quick.svg?style=flat&label=Fork)](https://github.com/vector4wang/spring-boot-quick/fork) # 前言   自己很早就想搞一个总的仓库就是将自己平时遇到的和学习到的东西整合在一起,方便后面用的时候快速的查找与使用,之前是通过branch 的方式去整理,感觉不直观,一咬牙就花了点时间将所有的分支整合在一起(之前使用branch方式的兄dei,这里对不住了,你们可以将之前的干掉,重新克隆总的),方便自己也方便大家参考,以下是我的相关博客,有兴趣的 可以去浏览浏览,觉得对自己有点启发或者解决了一些问题,可以点个赞~ -- CSDN http://blog.csdn.net/qqhjqs?viewmode=contents -- 简书 http://www.jianshu.com/u/223a1314e818 -- BLOG http://vector4wang.tk +[![](https://img.shields.io/badge/CSDN-@%E7%88%86%E7%B1%B3%E8%8A%B1%E6%9C%BA%E6%9E%AA%E6%89%8B-red.svg)](http://blog.csdn.net/qqhjqs?viewmode=list) +[![](https://img.shields.io/badge/BLOG-@%E5%86%AC%E4%B8%8E%E6%99%A8-lightgrey.svg)](http://blog.wangxc.club) +[![](https://img.shields.io/badge/%E7%AE%80-@%E5%86%AC%E4%B8%8E%E6%99%A8-E9705E.svg)](https://www.jianshu.com/u/223a1314e818) +[![](https://img.shields.io/badge/Github-@vector4wang-25292E.svg)](https://github.com/vector4wang) +[![](https://img.shields.io/badge/%E7%A0%81%E4%BA%91-@BMHJQS-C5212A.svg)](https://gitee.com/backwxc) ```bash . -├── log -├── logs +├── README.md ├── pom.xml +├── quick-activemq +├── quick-activemq2 +├── quick-async ├── quick-batch -├── quick-crawler -├── quick-ElasticSearch +├── quick-cache +├── quick-config-encrypt +├── quick-docker +├── quick-dubbo +├── quick-dynamic-bean ├── quick-exception -├── quick-idea +├── quick-feign +├── quick-hbase ├── quick-img2txt ├── quick-jdbc +├── quick-jpa ├── quick-jsp +├── quick-jwt +├── quick-kafka ├── quick-log +├── quick-logback +├── quick-lombok ├── quick-modules +├── quick-monitor-thread ├── quick-multi-data +├── quick-multi-rabbitmq ├── quick-mybatis-druid +├── quick-oauth2 ├── quick-okhttp ├── quick-oss ├── quick-package-assembly ├── quick-package-assembly-multi-env ├── quick-rabbitmq ├── quick-redies -├── quick-simhash -├── quick-spark +├── quick-rest-template +├── quick-rocketmq +├── quick-starter +├── quick-starter-demo ├── quick-swagger -├── quick-thread -├── quick-tika -├── quick-wx-api -├── README.md -└── spring-boot-quick.iml +├── quick-vue +├── quick-vw-crawler +└── quick-wx-public ``` @@ -74,10 +112,11 @@ private Date birthday; // 出生日期 ``` +swagger升级到2.7版本,添加了使用swagger验证功能,注意这里不是auth2鉴权 ## quick-thread -java线程池Executor的示例 +java线程池Executor的示例 [点我](https://github.com/vector4wang/java-learning-quick/tree/master/src/feature_learn/thread/future) ## quick-modules   @@ -105,10 +144,12 @@ springboot整合mybatis和阿里云的druid监控功能,日志可以控制sql | GET | /trace | 查看基本追踪信息 | true | ## quick-multi-data - springboot mybatis 多数据源配置,别忘了为多数据源的连接池配置 +- springboot mybatis 多数据源配置,别忘了为多数据源的连接池配置 +- 改为druid多数据源,方便监控数据库查询和性能 + ## quick-spark -springboot整合spark示例 +springboot整合spark示例 已抽离 [点我](https://github.com/vector4wang/quick-spark-process/tree/master/sb-word-count) ## quick-package-assembly @@ -128,7 +169,7 @@ apache的文本抽取开源框架,整合到springboot中 关于es搜索的相关内容     现在在学习Elasticsearch+Logstash+Kibana 后续会有相关博文、和代码示例~ -注意:想要用好es,搜索是关键 +注意:想要用好es,搜索是关键 移步[这里](https://github.com/vector4wang/quick-elasticsearch) ## quick-img2txt 图片与文字转换的程序示例 @@ -139,8 +180,8 @@ apache的文本抽取开源框架,整合到springboot中 ![转换前](https://ooo.0o0.ooo/2017/06/11/593c2c1d64882.jpg) ![转换后](https://ooo.0o0.ooo/2017/06/11/593c2a4b4980f.jpg) -- **验证码识别** 使用easyocr(项目地址)提供的api接口,可以识别验证码,这简直是小虫子的福利,就问你怕不怕! -- 自己在阿里云搭了一个服务,可以在线转换,自己做着玩玩,有兴趣的可以试一试,入口->[传送门](http://60.205.191.82:8001/img2txt) +- **验证码识别** 使用easyocr(项目地址)提供的api接口,可以识别验证码,这简直是小虫子的福利,就问你怕不怕!(现在收费了。。。) + ## quick-batch @@ -154,6 +195,12 @@ rabbit模块 ![ui](http://upload-images.jianshu.io/upload_images/3167229-945c72c2569f754a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +应网友@Android-BRUCE 的要求,添加了延迟队列的处理方式,包括针对消息和针对队列的处理方式 + +[![WX20180613-233153@2x.png](https://i.loli.net/2018/06/13/5b213948c6e1a.png)](https://i.loli.net/2018/06/13/5b213948c6e1a.png) + +一定要把图看懂。。。 +**友情提示:如果在刚开始queue与exchange绑定错了强烈建议从Rabbit管理页面上删除对应的配置再测试!!!** ## quick-exception @@ -177,56 +224,110 @@ springboot下统一处理异常方法,即,在请求没到达对应controller - 然后在pom文件中添加一下配置,目的是为了让webapp下的jsp文件留在META-INF中,让boot访问到 ```xml - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - - - - - - - src/main/webapp - - META-INF/resources - - **/** - - - - src/main/resources - - **/** - - false - - + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + + false + + + + + + + + src/main/webapp + + META-INF/resources + + **/** + + + + src/main/resources + + **/** + + false + + ``` ## quick-redis 工作上用到了redis,就添加了redis模块,很简单有key就获取,没有就插入 +## quick-rocketmq +此处的不是Apache 的Rocketmq,是阿里云服务上的一个消息队列 + +## quick-logback +springboot內置的日志管理模块,使用slf4j + +## quick-activemq +- springboot 整合activemq 服务 非常简单,更负责的配置可以自定义containerFactory +- 添加导出消息队列消息但不消费的方法(慎重使用,不知是否会对mq服务增加压力) + +## quick-docker + +使用docker部署springboot的示例demo,更多内容请见blog + +## quick-dynamic-bean +根据条件动态的创建bean +用到的场景:有的时候本地测试不想使用mq,可以在将mq对应consumer bean 加上contional注解,并 +配置条件 + +2019年03月14日00:12:54更新 +动态创建bean的方式: +- 动态设置属性; +- 动态设置属性文件,来改变要生成bean上的注解内容,如@Jmslistener(des="${key}"),我们可以动态改变key来生成不同的bean + +## quick-dubbo +整合dubbo,用的不是官方的dubbo-stater,用的是这个https://github.com/halober/spring-boot-starter-dubbo 配置很详细 +注意dubbo的结构 + +[![dubbo admin ui](https://i.loli.net/2018/05/25/5b07728002ea8.jpg)](https://i.loli.net/2018/05/25/5b07728002ea8.jpg) + +--- + --- +## quick-vw-crawler + +结合比较流行的框架SpringBoot抓取CSDN的数据(有关的Spingboot的使用可以参考[这里](https://github.com/vector4wang/spring-boot-quick)) +有关VW-Cralwer的介绍可以看[这里](https://github.com/vector4wang/vw-crawler),简单轻便开源的一款Java爬虫框架。 + +## quick-feign + +单独使用feign做为请求http接口的客户端工具 + (后面会持续更新) +## quick-config-encrypt +配置加密功能(待完成) + +## quick-jwt +参考https://github.com/murraco/spring-boot-jwt 添加的jwt模块,实现网站的简单鉴权 + +## quick-hbase +使用docker搭建hbase环境,springboot集成hbase client,完成基本的CURD; + +## quick-kafka +使用docker搭建的kafka环境,springboot集成kafka,完成消息的接受和发送 +## quick-starter +一个开发xxx-starter的示例,帮助你快速开发和部署自己的xxx-starter ### 温馨提示   如果您自己想在本地跑一跑,可以将其checkout到本地,直接`mvn clean install -U` 就ok了,如果您只想运行某个模块,直接复制配置文件和代码就ok了,如果您在测试某个模块但该模块出错,请尽快联系本人,邮箱:**vector4wang@qq.com**,我会在第一时间将其修复 -欢迎star和fork +欢迎star、fork 和 pr diff --git a/images/qiafan.png b/images/qiafan.png new file mode 100644 index 00000000..c8202c95 Binary files /dev/null and b/images/qiafan.png differ diff --git a/mybatis-crypt-plugin-test/pom.xml b/mybatis-crypt-plugin-test/pom.xml new file mode 100644 index 00000000..c7ec94a9 --- /dev/null +++ b/mybatis-crypt-plugin-test/pom.xml @@ -0,0 +1,86 @@ + + + mybatis-crypt-plugin-test + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + 8 + 8 + + + + + com.quick + mybatis-crypt-plugin + 1.0-SNAPSHOT + + + org.mybatis + mybatis + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.0.1 + + + + org.springframework.boot + spring-boot-starter-test + + + + mysql + mysql-connector-java + + + + tk.mybatis + mapper-spring-boot-starter + 2.1.2 + + + com.github.pagehelper + pagehelper + 4.1.5 + + + com.github.jsqlparser + jsqlparser + + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + + + + junit + junit + test + + + \ No newline at end of file diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/CryptApplication.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/CryptApplication.java new file mode 100644 index 00000000..8ee27373 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/CryptApplication.java @@ -0,0 +1,28 @@ +package com.quick.crypt.test; + + +import com.quick.crypt.test.config.CustomerCryptor; +import com.quick.db.crypt.annotation.EnableEncrypt; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import tk.mybatis.spring.annotation.MapperScan; + +import javax.annotation.PostConstruct; + +@SpringBootApplication +@MapperScan(basePackages = "com.quick.crypt.test.dao") +@Slf4j +@EnableEncrypt(value = "123", encryptIml = CustomerCryptor.class) +public class CryptApplication { + + @PostConstruct + public void test() { + log.info("after start !"); + } + + public static void main(String[] args) { + SpringApplication.run(CryptApplication.class); + } + +} \ No newline at end of file diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BaseMapper.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BaseMapper.java new file mode 100644 index 00000000..35f65ddb --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BaseMapper.java @@ -0,0 +1,8 @@ +package com.quick.crypt.test.base; + +import tk.mybatis.mapper.common.Mapper; +import tk.mybatis.mapper.common.MySqlMapper; + +public interface BaseMapper extends Mapper, MySqlMapper { + +} diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BaseService.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BaseService.java new file mode 100644 index 00000000..0ac35cc1 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BaseService.java @@ -0,0 +1,91 @@ +package com.quick.crypt.test.base; + +import com.github.pagehelper.PageInfo; + +import java.util.List; + +public interface BaseService { + /** + * 主键查询 + * + * @param key + * @return + */ + T selectByKey(Object key); + + /** + * 保存实体 + * + * @param entity + * @return + */ + int save(T entity) throws MessageException; + + /** + * 主键删除 + * + * @param key + * @return + */ + int delete(Object key) throws MessageException; + + /** + * 条件删除 + * + * @param example + * @return + */ + int deleteByExample(BeanCriteria example) throws MessageException; + + /** + * 更新所有字段 + * + * @param entity + * @return + */ + int updateAll(T entity) throws MessageException; + + /** + * 更新选中字段 + * + * @param entity + * @return + */ + int updateNotNull(T entity) throws MessageException; + + /** + * 条件查询 + * + * @param example + * @return + */ + List selectByExample(BeanCriteria example); + + /** + * 分页查询,带条件 + * + * @param page + * @param example + * @return + */ + PageInfo selectByPage(PageInfo page, BeanCriteria example); + + /** + * 分页查询,不带条件 + * + * @param page + * @return + */ + PageInfo selectByPage(PageInfo page); + + int update(T entity) throws MessageException; + + List getByProperty(String property, Object value,Class clazz); + /** + * @Description: 条件更新 + * @Author: 01397368-hq + * @Date: 2020/11/25 16:30 + */ + int updateByExampleSelective(T record, Object example); + +} diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BaseServiceImpl.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BaseServiceImpl.java new file mode 100644 index 00000000..87a80465 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BaseServiceImpl.java @@ -0,0 +1,105 @@ +package com.quick.crypt.test.base; + +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.List; + + +@Slf4j +public class BaseServiceImpl implements BaseService { + + + @Autowired + private BaseMapper mapper; + + public BaseMapper getMapper() { + return mapper; + } + + @Override + public T selectByKey(Object key) { + return mapper.selectByPrimaryKey(key); + } + + @Override + public int save(T entity) throws MessageException { + return mapper.insert(entity); + } + + @Override + public int update(T entity) throws MessageException { + return mapper.updateByPrimaryKey(entity); + } + + @Override + public int delete(Object key) throws MessageException { + return mapper.deleteByPrimaryKey(key); + } + + @Override + public int deleteByExample(BeanCriteria example) throws MessageException { + return mapper.deleteByExample(example); + } + + @Override + public int updateAll(T entity) throws MessageException { + return mapper.updateByPrimaryKey(entity); + } + + @Override + public int updateNotNull(T entity) throws MessageException { + return mapper.updateByPrimaryKeySelective(entity); + } + + @Override + public List selectByExample(BeanCriteria example) { + return mapper.selectByExample(example); + } + + @Override + public PageInfo selectByPage(PageInfo page, BeanCriteria example) { + PageHelper.startPage(page.getPageNum(), page.getPageSize()); + List list = mapper.selectByExample(example); + return new PageInfo(list); + } + + @Override + public PageInfo selectByPage(PageInfo page) { + PageHelper.startPage(page.getPageNum(), page.getPageSize()); + List list = mapper.selectAll(); + return new PageInfo(list); + } + + @Override + public List getByProperty(String property, Object value, Class clazz) { + BeanCriteria beanCriteria = new BeanCriteria(clazz); + BeanCriteria.Criteria criteria = beanCriteria.createCriteria(); + if (property != null && !"".equals(property)) { + try { + PropertyDescriptor pd = new PropertyDescriptor(property, clazz); + Method getter = pd.getReadMethod(); + if (getter != null) { + criteria.andEqualTo(property, value); + } else { + return null; + } + } catch (IntrospectionException e) { + log.warn(clazz.getName() + " has no property of " + property); + } + } + return this.selectByExample(beanCriteria); + } + + + @Override + public int updateByExampleSelective(T record, Object example) { + return mapper.updateByExampleSelective(record, example); + } + +} diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BeanCriteria.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BeanCriteria.java new file mode 100644 index 00000000..090d1d56 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/BeanCriteria.java @@ -0,0 +1,487 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 abel533@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.quick.crypt.test.base; + +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.apache.ibatis.type.TypeHandler; +import tk.mybatis.mapper.entity.EntityColumn; +import tk.mybatis.mapper.entity.EntityTable; +import tk.mybatis.mapper.mapperhelper.EntityHelper; + +import java.util.*; + +public class BeanCriteria { + protected String orderByClause; + + protected boolean distinct; + + protected boolean exists; + + protected Set selectColumns; + + protected List oredCriteria; + + protected Class entityClass; + + protected EntityTable table; + // 属性和列对应 + protected Map propertyMap; + + /** + * 默认exists为true + * + * @param entityClass + */ + public BeanCriteria(Class entityClass) { + this(entityClass, true); + } + + /** + * 带exists参数的构造方法 + * + * @param entityClass + * @param exists + * - true时,如果字段不存在就抛出异常,false时,如果不存在就不使用该字段的条件 + */ + public BeanCriteria(Class entityClass, boolean exists) { + this.exists = exists; + oredCriteria = new ArrayList(); + this.entityClass = entityClass; + table = EntityHelper.getEntityTable(entityClass); + propertyMap = new HashMap(table.getEntityClassColumns().size()); + for (EntityColumn column : table.getEntityClassColumns()) { + propertyMap.put(column.getProperty(), column); + } + } + + public Class getEntityClass() { + return entityClass; + } + + public String getOrderByClause() { + return orderByClause; + } + + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; + } + + public Set getSelectColumns() { + return selectColumns; + } + + /** + * 指定要查询的属性列 - 这里会自动映射到表字段 + * + * @param properties + * @return + */ + public BeanCriteria selectProperties(String... properties) { + if (properties != null && properties.length > 0) { + if (this.selectColumns == null) { + this.selectColumns = new LinkedHashSet(); + } + for (String property : properties) { + if (propertyMap.containsKey(property)) { + this.selectColumns.add(propertyMap.get(property).getColumn()); + } + } + } + return this; + } + + public boolean isDistinct() { + return distinct; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public List getOredCriteria() { + return oredCriteria; + } + + public void or(Criteria criteria) { + oredCriteria.add(criteria); + } + + public Criteria or() { + Criteria criteria = createCriteriaInternal(); + oredCriteria.add(criteria); + return criteria; + } + + public Criteria createCriteria() { + Criteria criteria = createCriteriaInternal(); + if (oredCriteria.size() == 0) { + oredCriteria.add(criteria); + } + return criteria; + } + + protected Criteria createCriteriaInternal() { + Criteria criteria = new Criteria(propertyMap, exists); + return criteria; + } + + public void clear() { + oredCriteria.clear(); + orderByClause = null; + distinct = false; + } + + protected abstract static class GeneratedCriteria { + protected List criteria; + // 字段是否必须存在 + protected boolean exists; + // 属性和列对应 + protected Map propertyMap; + + protected GeneratedCriteria(Map propertyMap) { + this(propertyMap, true); + } + + protected GeneratedCriteria(Map propertyMap, boolean exists) { + super(); + this.exists = exists; + criteria = new ArrayList(); + this.propertyMap = propertyMap; + } + + private String column(String property) { + if (propertyMap.containsKey(property)) { + return propertyMap.get(property).getColumn(); + } else if (exists) { +// throw new RuntimeException("当前实体类不包含名为" + property + "的属性!"); + throw new RuntimeException("当前实体类不包含名为" + property + "的属性!"); + } else { + return null; + } + } + + private String property(String property) { + if (propertyMap.containsKey(property)) { + return property; + } else if (exists) { +// throw new RuntimeException("当前实体类不包含名为" + property + "的属性!"); + throw new RuntimeException("当前实体类不包含名为" + property + "的属性!"); + } else { + return null; + } + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List getCriteria() { + return criteria; + } + + protected void addCriterion(String condition) { + if (condition == null) { + throw new RuntimeException("Value for condition cannot be null"); + } + if (condition.startsWith("null")) { + return; + } + criteria.add(new Criterion(condition)); + } + + protected void addCriterion(String condition, Object value, String property) { + if (value == null) { + throw new RuntimeException("Value for " + property + " cannot be null"); + } + if (property == null) { + return; + } + criteria.add(new Criterion(condition, value)); + } + + protected void addCriterion(String condition, Object value1, Object value2, String property) { + if (value1 == null || value2 == null) { + throw new RuntimeException("Between values for " + property + " cannot be null"); + } + if (property == null) { + return; + } + criteria.add(new Criterion(condition, value1, value2)); + } + + public Criteria andIsNull(String property) { + addCriterion(column(property) + " is null"); + return (Criteria) this; + } + + public Criteria andIsNotNull(String property) { + addCriterion(column(property) + " is not null"); + return (Criteria) this; + } + + public Criteria andEqualTo(String property, Object value) { + addCriterion(column(property) + " =", value, property(property)); + return (Criteria) this; + } + + public Criteria andNotEqualTo(String property, Object value) { + addCriterion(column(property) + " <>", value, property(property)); + return (Criteria) this; + } + + public Criteria andGreaterThan(String property, Object value) { + addCriterion(column(property) + " >", value, property(property)); + return (Criteria) this; + } + + public Criteria andGreaterThanOrEqualTo(String property, Object value) { + addCriterion(column(property) + " >=", value, property(property)); + return (Criteria) this; + } + + public Criteria andLessThan(String property, Object value) { + addCriterion(column(property) + " <", value, property(property)); + return (Criteria) this; + } + + public Criteria andLessThanOrEqualTo(String property, Object value) { + addCriterion(column(property) + " <=", value, property(property)); + return (Criteria) this; + } + + public Criteria andIn(String property, List values) { + addCriterion(column(property) + " in", values, property(property)); + return (Criteria) this; + } + + public Criteria andNotIn(String property, List values) { + addCriterion(column(property) + " not in", values, property(property)); + return (Criteria) this; + } + + public Criteria andBetween(String property, Object value1, Object value2) { + addCriterion(column(property) + " between", value1, value2, property(property)); + return (Criteria) this; + } + + public Criteria andNotBetween(String property, Object value1, Object value2) { + addCriterion(column(property) + " not between", value1, value2, property(property)); + return (Criteria) this; + } + + public Criteria andLike(String property, String value) { + addCriterion(column(property) + " like", value, property(property)); + return (Criteria) this; + } + + public Criteria andNotLike(String property, String value) { + addCriterion(column(property) + " not like", value, property(property)); + return (Criteria) this; + } + + /** + * 手写条件 + * + * @param condition + * 例如 "length(countryname)<5" + * @return + */ + public Criteria andCondition(String condition) { + addCriterion(condition); + return (Criteria) this; + } + + /** + * 手写左边条件,右边用value值 + * + * @param condition + * 例如 "length(countryname)=" + * @param value + * 例如 5 + * @return + */ + public Criteria andCondition(String condition, Object value) { + criteria.add(new Criterion(condition, value)); + return (Criteria) this; + } + + /** + * 手写左边条件,右边用value值 + * + * @param condition + * 例如 "length(countryname)=" + * @param value + * 例如 5 + * @param typeHandler + * 类型处理 + * @return + */ + public Criteria andCondition(String condition, Object value, String typeHandler) { + criteria.add(new Criterion(condition, value, typeHandler)); + return (Criteria) this; + } + + /** + * 手写左边条件,右边用value值 + * + * @param condition + * 例如 "length(countryname)=" + * @param value + * 例如 5 + * @param typeHandler + * 类型处理 + * @return + */ + public Criteria andCondition(String condition, Object value, @SuppressWarnings("rawtypes") Class typeHandler) { + criteria.add(new Criterion(condition, value, typeHandler.getCanonicalName())); + return (Criteria) this; + } + + /** + * 将此对象的不为空的字段参数作为相等查询条件 + * + * @param param + * 参数对象 + * @Date 2015年7月17日 下午12:48:08 + */ + public Criteria andEqualTo(Object param) { + MetaObject metaObject = SystemMetaObject.forObject(param); + String[] properties = metaObject.getGetterNames(); + for (String property : properties) { + // 属性和列对应Map中有此属性 + if (propertyMap.get(property) != null) { + Object value = metaObject.getValue(property); + // 属性值不为空 + if (value != null) { + andEqualTo(property, value); + } + } + } + return (Criteria) this; + } + } + + public static class Criteria extends GeneratedCriteria { + protected Criteria(Map propertyMap) { + super(propertyMap); + } + + protected Criteria(Map propertyMap, boolean exists) { + super(propertyMap, exists); + } + } + + public static class Criterion { + private String condition; + + private Object value; + + private Object secondValue; + + private boolean noValue; + + private boolean singleValue; + + private boolean betweenValue; + + private boolean listValue; + + private String typeHandler; + + protected Criterion(String condition) { + super(); + this.condition = condition; + this.typeHandler = null; + this.noValue = true; + } + + protected Criterion(String condition, Object value, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.typeHandler = typeHandler; + if (value instanceof List) { + this.listValue = true; + } else { + this.singleValue = true; + } + } + + protected Criterion(String condition, Object value) { + this(condition, value, null); + } + + protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.secondValue = secondValue; + this.typeHandler = typeHandler; + this.betweenValue = true; + } + + protected Criterion(String condition, Object value, Object secondValue) { + this(condition, value, secondValue, null); + } + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } + } +} + diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/MessageException.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/MessageException.java new file mode 100644 index 00000000..a643c857 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/base/MessageException.java @@ -0,0 +1,14 @@ +package com.quick.crypt.test.base; + +public class MessageException extends Exception { + + private static final long serialVersionUID = 1L; + + public MessageException(Exception exception) { + super(exception.getMessage(), exception.getCause()); + } + + public MessageException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/config/CustomerCryptor.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/config/CustomerCryptor.java new file mode 100644 index 00000000..16dcff8f --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/config/CustomerCryptor.java @@ -0,0 +1,30 @@ +package com.quick.crypt.test.config; + +import com.quick.db.crypt.encrypt.BaseEncrypt; + +/** + * @author 01390942 + * @Description + * @create 2022/5/25 + * @since 1.0.0 + */ +public class CustomerCryptor extends BaseEncrypt { + + public CustomerCryptor() { + super(); + } + + public CustomerCryptor(String password) { + super(password); + } + + @Override + public String encrypt(String src) { + return src + " 使用 " + password + "已加密"; + } + + @Override + public String decrypt(String encrypt) { + return encrypt + " 使用" + password + "已解密"; + } +} \ No newline at end of file diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/dao/UserDao.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/dao/UserDao.java new file mode 100644 index 00000000..527fa3d8 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/dao/UserDao.java @@ -0,0 +1,83 @@ +package com.quick.crypt.test.dao; + +import com.quick.crypt.test.base.BaseMapper; +import com.quick.crypt.test.entity.User; +import org.apache.ibatis.annotations.Param; + +import java.util.Collection; +import java.util.List; + +/** + * (User)表数据库访问层 + * + * @author makejava + * @since 2022-05-18 16:51:53 + */ +public interface UserDao extends BaseMapper { + + /** + * 通过ID查询单条数据 + * + * @param id 主键 + * @return 实例对象 + */ + User queryById(@Param("id") Integer id); + + + + /** + * 统计总行数 + * + * @param user 查询条件 + * @return 总行数 + */ + long count(@Param("user") User user); + + /** + * 新增数据 + * + * @param user 实例对象 + * @return 影响行数 + */ + int insert(@Param("user") User user); + + /** + * 批量新增数据(MyBatis原生foreach方法) + * + * @param entities List 实例对象列表 + * @return 影响行数 + */ + int insertBatch(@Param("entities") Collection entities); + + /** + * 批量新增或按主键更新数据(MyBatis原生foreach方法) + * + * @param entities List 实例对象列表 + * @return 影响行数 + * @throws org.springframework.jdbc.BadSqlGrammarException 入参是空List的时候会抛SQL语句错误的异常,请自行校验入参 + */ + int insertOrUpdateBatch(@Param("entities") List entities); + + /** + * 修改数据 + * + * @param user 实例对象 + * @return 影响行数 + */ + int update(@Param("user") User user); + + /** + * 通过主键删除数据 + * + * @param id 主键 + * @return 影响行数 + */ + int deleteById(@Param("id") Integer id); + + List findAll(); + + int insertStr(@Param("name") String name, @Param("phone") String phone); + +// int batchSetInsert(@Param("sets") Set sets); +} + diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/entity/User.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/entity/User.java new file mode 100644 index 00000000..2ad187b4 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/entity/User.java @@ -0,0 +1,32 @@ +package com.quick.crypt.test.entity; + +import com.quick.db.crypt.annotation.CryptEntity; +import com.quick.db.crypt.annotation.CryptField; +import lombok.Data; + +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * (User)实体类 + * + * @author makejava + * @since 2022-05-18 16:51:55 + */ +@Data +@CryptEntity +@Table(name = "user") +public class User implements Serializable { + private static final long serialVersionUID = -33370517147127073L; + + @Id + private Integer id; + + private String name; + + @CryptField + private String phone; + +} + diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/service/UserService.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/service/UserService.java new file mode 100644 index 00000000..33c4c952 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/service/UserService.java @@ -0,0 +1,52 @@ +package com.quick.crypt.test.service; + +import com.quick.crypt.test.base.BaseService; +import com.quick.crypt.test.entity.User; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Set; + +/** + * (User)表服务接口 + * + * @author makejava + * @since 2022-05-18 16:52:01 + */ +public interface UserService extends BaseService { + + /** + * 通过ID查询单条数据 + * + * @param id 主键 + * @return 实例对象 + */ + User queryById(Integer id); + + + + /** + * 新增数据 + * + * @param user 实例对象 + * @return 实例对象 + */ + User insert(User user); + + int batchInsert(List userList); + + + /** + * 通过主键删除数据 + * + * @param id 主键 + * @return 是否成功 + */ + boolean deleteById(Integer id); + + List findAll(); + + int insert(String vector, String s); + + int batchSetInsert(Set sets); +} diff --git a/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/service/impl/UserServiceImpl.java b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/service/impl/UserServiceImpl.java new file mode 100644 index 00000000..e8cd85da --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/java/com/quick/crypt/test/service/impl/UserServiceImpl.java @@ -0,0 +1,79 @@ +package com.quick.crypt.test.service.impl; + +import com.quick.crypt.test.base.BaseServiceImpl; +import com.quick.crypt.test.dao.UserDao; +import com.quick.crypt.test.entity.User; +import com.quick.crypt.test.service.UserService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Set; + +/** + * (User)表服务实现类 + * + * @author makejava + * @since 2022-05-18 16:52:03 + */ +@Service("userService") +public class UserServiceImpl extends BaseServiceImpl implements UserService { + @Resource + private UserDao userDao; + + /** + * 通过ID查询单条数据 + * + * @param id 主键 + * @return 实例对象 + */ + @Override + public User queryById(Integer id) { + return this.userDao.queryById(id); + } + + + + /** + * 新增数据 + * + * @param user 实例对象 + * @return 实例对象 + */ + @Override + public User insert(User user) { + this.userDao.insert(user); + return user; + } + + @Override + public int batchInsert(List userList) { + return this.userDao.insertBatch(userList); + } + + /** + * 通过主键删除数据 + * + * @param id 主键 + * @return 是否成功 + */ + @Override + public boolean deleteById(Integer id) { + return this.userDao.deleteById(id) > 0; + } + + @Override + public List findAll() { + return userDao.findAll(); + } + + @Override + public int insert(String name, String phone) { + return userDao.insertStr(name, phone); + } + + @Override + public int batchSetInsert(Set sets) { + return userDao.insertBatch(sets); + } +} diff --git a/mybatis-crypt-plugin-test/src/main/resources/application.properties b/mybatis-crypt-plugin-test/src/main/resources/application.properties new file mode 100644 index 00000000..4273acbe --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/resources/application.properties @@ -0,0 +1,34 @@ +server.port=8080 + +#DATASOURCE use docker [docker run --name sb-quick-mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3307:3306 -d mysql:5.7] +spring.datasource.url=jdbc:mysql://localhost:3306/wold +spring.datasource.username=root +spring.datasource.password=123456 +## 5.7 +#spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.datasource.initialSize=5 +spring.datasource.minIdle=5 +spring.datasource.maxActive=20 +spring.datasource.maxWait=60000 +spring.datasource.timeBetweenEvictionRunsMillis=60000 +spring.datasource.validationQuery=SELECT 1 +spring.datasource.testWhileIdle=true +spring.datasource.testOnBorrow=false +spring.datasource.testOnReturn=false +spring.datasource.poolPreparedStatements=true +spring.datasource.maxPoolPreparedStatementPerConnectionSize=20 +spring.datasource.filters=stat +spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 + + +#MYBATIS +mybatis.type-aliases-package=com.quick.crypt.test.dao +#mybatis.mapper-locations=test-classpath*:/mapper/*Dao.xml +mybatis.mapper-locations=classpath*:/mapper/*Dao.xml + +mybatis.configuration.map-underscore-to-camel-case=true +mybatis.configuration.use-generated-keys=true +mybatis.configuration.default-fetch-size=100 +mybatis.configuration.default-statement-timeout=30 \ No newline at end of file diff --git a/mybatis-crypt-plugin-test/src/main/resources/mapper/UserDao.xml b/mybatis-crypt-plugin-test/src/main/resources/mapper/UserDao.xml new file mode 100644 index 00000000..d8c551e7 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/resources/mapper/UserDao.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + insert into user(name, phone) + values (#{user.name}, #{user.phone}) + + + + insert into user(name, phone) + values + + (#{entity.name}, #{entity.phone}) + + + + + insert into user(name, phone) + values + + (#{entity.name}, #{entity.phone}) + + on duplicate key update + name = values(name), + phone = values(phone) + + + + insert into user(name, phone) + values (#{name}, #{phone}) + + + + + + + + + + + + + + + update user + + + name = #{name}, + + + phone = #{phone}, + + + where id = #{id} + + + + + delete from user where id = #{id} + + + + diff --git a/mybatis-crypt-plugin-test/src/main/resources/wold.sql b/mybatis-crypt-plugin-test/src/main/resources/wold.sql new file mode 100644 index 00000000..3b30abe1 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/main/resources/wold.sql @@ -0,0 +1,52 @@ +/* + Navicat Premium Data Transfer + + Source Server : localhost + Source Server Type : MySQL + Source Server Version : 50738 + Source Host : localhost:3306 + Source Schema : wold + + Target Server Type : MySQL + Target Server Version : 50738 + File Encoding : 65001 + + Date: 17/05/2022 19:20:50 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for address +-- ---------------------------- +DROP TABLE IF EXISTS `address`; +CREATE TABLE `address` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NULL DEFAULT NULL, + `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of address +-- ---------------------------- +INSERT INTO `address` VALUES (1, 1, '中国西北尼玛县'); + +-- ---------------------------- +-- Table structure for user +-- ---------------------------- +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of user +-- ---------------------------- +INSERT INTO `user` VALUES (1, 'vector', '17777777777'); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/mybatis-crypt-plugin-test/src/test/java/DbTest.java b/mybatis-crypt-plugin-test/src/test/java/DbTest.java new file mode 100644 index 00000000..69f89154 --- /dev/null +++ b/mybatis-crypt-plugin-test/src/test/java/DbTest.java @@ -0,0 +1,128 @@ +import cn.hutool.core.util.RandomUtil; +import com.quick.crypt.test.CryptApplication; +import com.quick.crypt.test.base.BeanCriteria; +import com.quick.crypt.test.entity.User; +import com.quick.crypt.test.service.UserService; +import com.quick.db.crypt.encrypt.Encrypt; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.LongStream; + +@SpringBootTest(classes = CryptApplication.class) +@RunWith(SpringJUnit4ClassRunner.class) +@Slf4j +public class DbTest { + + @Resource + private UserService userService; + + @Autowired + private Encrypt encrypt; + + @Test + public void testEncrypt() { + System.out.println(encrypt.encrypt("13333333333")); + System.out.println(encrypt.decrypt("13333333333")); + } + + @Test + public void testInsert() { +// User vector = User.builder().name("vector").phone("13333333333").build(); +// LongStream.range(0, 10).forEach(k -> { +// System.out.println(k); + User vector = new User(); + vector.setName("vector" + 1); + vector.setPhone("13333333333"); + log.info("wait crypt: {}", vector); + User insert = userService.insert(vector); + log.info("insert obj {}", insert); +// }); + } + + @Test + public void testUpdate() { + BeanCriteria beanCriteria = new BeanCriteria(User.class); + beanCriteria.createCriteria().andEqualTo("id", 153); + + User updateUser = new User(); + updateUser.setPhone("17727826853"); + + userService.updateByExampleSelective(updateUser, beanCriteria); + + } + + @Test + public void testInsertStr() { + + String vector = encrypt.encrypt("vector"); + userService.insert(vector, "13333333333"); + } + + @Test + public void testBatchInsert() { + + + +// User vector = User.builder().name("vector").phone("13333333333").build(); + List list = new ArrayList<>(); + LongStream.range(0, 10).forEach(k -> { + System.out.println(k); + User vector = new User(); + vector.setName("vector" + k); + vector.setPhone("13333333333"); + + list.add(vector); + }); + int sum = userService.batchInsert(list); + log.info("insert obj {}", sum); + } + + @Test + public void testBatchSetInsert() { +// User vector = User.builder().name("vector").phone("13333333333").build(); + Set sets = new HashSet<>(); + LongStream.range(0, 10).forEach(k -> { + System.out.println(k); + User vector = new User(); + vector.setName("vector" + k); + vector.setPhone(RandomUtil.randomNumbers(11)); + + sets.add(vector); + }); + int sum = userService.batchSetInsert(sets); + log.info("insert obj {}", sum); + } + + @Test + public void testQueryById() { + User user = userService.queryById(164); + log.info("query: {}", user); + } + + @Test + public void testQueryAll() { + List userList = userService.findAll(); + log.info("query: {}", userList); + } + +// @Test +// public void testDeencrypt() throws NoSuchAlgorithmException { +// /** +// * 98929429633--->8720d9eb197889fe7761ed03dc455ea5 +// */ +// +// AesDesDefaultEncrypt aesDesDefaultEncrypt = new AesDesDefaultEncrypt(); +//// System.out.println(aesDesDefaultEncrypt.decrypt("0e22e227f48d13e17baa500f68e72024")); +// System.out.println(aesDesDefaultEncrypt.encrypt("17727826853")); +// } +} \ No newline at end of file diff --git a/mybatis-crypt-plugin/README.md b/mybatis-crypt-plugin/README.md new file mode 100644 index 00000000..6e798ffe --- /dev/null +++ b/mybatis-crypt-plugin/README.md @@ -0,0 +1,114 @@ +### 本地docker启mysql +```bash +docker run --name mysql-server -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 +``` + + +### 参考 +- https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers +- https://www.cnblogs.com/felordcn/p/13473844.html + +### 注意 + +mybatis 3.5.6 +``` +org.apache.ibatis.executor.statement.StatementHandler 无 Statement prepare(Connection connection, Integer transactionTimeout) 方法 +``` + + +### 接入 +#### 1、直接引用 +在需要使用加解密的服务里,使用@EnableEncrypt,这里支持自定义密码,如@EnableEncrypt(value = "123"),但是不建议使用 + +然后在需要加解密的实体或者mapper方法返回对象中增加注解,如下 +```java +@Table(name = "user") +@Data +@CryptEntity +public class User { + 。。。 + @Column(name = "to_phone") + @CryptField + private String phone; + + 。。。 +} +``` + + +#### 2、自定义数据源 +对于显示自定义SqlSessionFactory的应用来说,需要手动将拦截器增加进去,如 + +```java +@Bean("sqlSessionFactory") +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); + sqlSessionFactoryBean.setDataSource(dataSource); + sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapper)); + sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); + PageHelper ph = new PageHelper(); + Properties defaults = new Properties(); + defaults.setProperty("returnPageInfo","check"); + ph.setProperties(defaults); + sqlSessionFactoryBean.getObject().getConfiguration().addInterceptor(ph); + + /** + * mybatis加解密增加拦截 + */ + Encrypt encrypt = new AesDesDefaultEncrypt(); + sqlSessionFactoryBean.getObject().getConfiguration().addInterceptor(new CryptResultInterceptor(encrypt)); + sqlSessionFactoryBean.getObject().getConfiguration().addInterceptor(new CryptParamInterceptor(encrypt)); + + return sqlSessionFactoryBean.getObject(); +} +``` + +原因:在源码的tk.mybatis.mapper.autoconfigure.MapperAutoConfiguration#sqlSessionFactory中,会自动的将Spring容器中所有的mybatis的Interceptor拦截器加入到factory中,所以,如果我们自定义sqlSessionFactory时,就要把插件引入的逻辑重写 +```java +@Bean +@ConditionalOnMissingBean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); + factory.setDataSource(dataSource); + factory.setVfs(SpringBootVFS.class); + if (StringUtils.hasText(this.properties.getConfigLocation())) { + factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); + } + applyConfiguration(factory); + if (this.properties.getConfigurationProperties() != null) { + factory.setConfigurationProperties(this.properties.getConfigurationProperties()); + } + // 自动将spring容器中的插件设置到了factory中 + if (!ObjectUtils.isEmpty(this.interceptors)) { + factory.setPlugins(this.interceptors); + } + if (this.databaseIdProvider != null) { + factory.setDatabaseIdProvider(this.databaseIdProvider); + } + if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { + factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); + } + if (this.properties.getTypeAliasesSuperType() != null) { + factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); + } + if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { + factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); + } + if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { + factory.setMapperLocations(this.properties.resolveMapperLocations()); + } + + return factory.getObject(); +} +``` + + + +#### 3、springmvc 配置引入 +对于springmvc项目可以这样引入 + + + + + + diff --git a/mybatis-crypt-plugin/pom.xml b/mybatis-crypt-plugin/pom.xml new file mode 100644 index 00000000..1e8f6785 --- /dev/null +++ b/mybatis-crypt-plugin/pom.xml @@ -0,0 +1,70 @@ + + + mybatis-crypt-plugin + 1.0-SNAPSHOT + 4.0.0 + jar + + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + 8 + 8 + + 3.4.2 + + 3.5.6 + + + + + org.springframework + spring-context + + + tk.mybatis + mapper + ${tk.mybatis.version} + + + + org.mybatis + mybatis + + ${org.mybatis.version} + + + + + + + + + + + + org.projectlombok + lombok + + + ch.qos.logback + logback-classic + + + + org.bouncycastle + bcprov-jdk15on + 1.70 + + + + + \ No newline at end of file diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/annotation/CryptEntity.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/annotation/CryptEntity.java new file mode 100644 index 00000000..4bb981ad --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/annotation/CryptEntity.java @@ -0,0 +1,10 @@ +package com.quick.db.crypt.annotation; + +import java.lang.annotation.*; + +@Target(value = ElementType.TYPE) +@Inherited +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface CryptEntity { +} diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/annotation/CryptField.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/annotation/CryptField.java new file mode 100644 index 00000000..2d123443 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/annotation/CryptField.java @@ -0,0 +1,11 @@ +package com.quick.db.crypt.annotation; + +import java.lang.annotation.*; + +@Target(value = ElementType.FIELD) +@Inherited +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface CryptField { + +} \ No newline at end of file diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/annotation/EnableEncrypt.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/annotation/EnableEncrypt.java new file mode 100644 index 00000000..45f3d84a --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/annotation/EnableEncrypt.java @@ -0,0 +1,29 @@ +package com.quick.db.crypt.annotation; + +import com.quick.db.crypt.config.EncryptConfig; +import com.quick.db.crypt.encrypt.AesDesDefaultEncrypt; +import com.quick.db.crypt.encrypt.Encrypt; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * @author vector4wang + * https://www.jianshu.com/p/c92a6a9d8c4f + * https://www.jianshu.com/p/3a2aac411edf + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Import(EncryptConfig.class) +public @interface EnableEncrypt { + /** + * 加密key,一般使用默认 + * + * @return + */ + String value() default ""; + + Class encryptIml() default AesDesDefaultEncrypt.class; + +} diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/config/EncryptConfig.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/config/EncryptConfig.java new file mode 100644 index 00000000..c8bc8d83 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/config/EncryptConfig.java @@ -0,0 +1,48 @@ +package com.quick.db.crypt.config; + + +import com.quick.db.crypt.annotation.EnableEncrypt; +import com.quick.db.crypt.encrypt.Encrypt; +import com.quick.db.crypt.intercept.CryptParamInterceptor; +import com.quick.db.crypt.intercept.CryptReadInterceptor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.StringUtils; + +/** + * 增加默认配置,通过@EnableEncrypt引入 + */ +@Slf4j +public class EncryptConfig implements ImportBeanDefinitionRegistrar { + + @Override + public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { + AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(EnableEncrypt.class.getName())); + String value = annoAttrs.getString("value"); + Class encryptIml = annoAttrs.getClass("encryptIml"); + + log.info("enable encrypt value: {}", value); + BeanDefinitionBuilder bdb0 = BeanDefinitionBuilder.rootBeanDefinition(encryptIml); + if (!StringUtils.isEmpty(value)) { + bdb0.addConstructorArgValue(value); + } + + /** + * 方便后续系统引入 + */ + registry.registerBeanDefinition("encrypt", bdb0.getBeanDefinition()); + + BeanDefinitionBuilder bdb1 = BeanDefinitionBuilder.rootBeanDefinition(CryptReadInterceptor.class); + bdb1.addConstructorArgReference("encrypt"); + registry.registerBeanDefinition("cryptReadInterceptor", bdb1.getBeanDefinition()); + + BeanDefinitionBuilder bdb2 = BeanDefinitionBuilder.rootBeanDefinition(CryptParamInterceptor.class); + bdb2.addConstructorArgReference("encrypt"); + registry.registerBeanDefinition("cryptParamInterceptor", bdb2.getBeanDefinition()); + + } +} diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/AesDesDefaultEncrypt.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/AesDesDefaultEncrypt.java new file mode 100644 index 00000000..56ab7859 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/AesDesDefaultEncrypt.java @@ -0,0 +1,96 @@ +package com.quick.db.crypt.encrypt; + +import lombok.extern.slf4j.Slf4j; +import com.quick.db.crypt.util.Hex; +import org.springframework.util.StringUtils; + +import javax.crypto.*; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +@Slf4j +public class AesDesDefaultEncrypt extends BaseEncrypt { + + private static final String DEFAULT_SEC = "FMjDV69Xkd6y9HVVK"; + private static final String KEY_ALGORITHM = "AES"; + private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding"; + + private String password; + private SecretKeySpec secretKeySpec; + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public AesDesDefaultEncrypt() throws NoSuchAlgorithmException { + log.info("init encrypt by default passwd"); + this.password = DEFAULT_SEC; + this.secretKeySpec = getSecretKey(this.password); + } + + public AesDesDefaultEncrypt(String password) throws NoSuchAlgorithmException { + + if (StringUtils.isEmpty(password)) { + throw new IllegalArgumentException("password should not be null!"); + } + log.info("init encrypt by custom passwd"); + this.password = password; + this.secretKeySpec = getSecretKey(password); + } + + @Override + public String encrypt(String value) { + + try { + Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + + byte[] content = value.getBytes("UTF-8"); + byte[] encryptData = cipher.doFinal(content); + + return Hex.bytesToHexString(encryptData); + } catch (Exception e) { + log.error("AES加密时出现问题,密钥为:{}", password); + throw new IllegalStateException("AES加密时出现问题" + e.getMessage(), e); + } + } + + @Override + public String decrypt(String value) { + if (StringUtils.isEmpty(value)) { + return ""; + } + try { + byte[] encryptData = Hex.hexStringToBytes(value); + Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + byte[] content = cipher.doFinal(encryptData); + return new String(content, "UTF-8"); + } catch (Exception e) { + log.error("AES解密时出现问题,密钥为:{},密文为:{}", password, value); + throw new IllegalStateException("AES解密时出现问题" + e.getMessage(), e); + } + + } + + + private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException { + //返回生成指定算法密钥生成器的 KeyGenerator 对象 + KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM); + //AES 要求密钥长度为 128 + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + random.setSeed(password.getBytes()); + kg.init(128, random); + //生成一个密钥 + SecretKey secretKey = kg.generateKey(); + // 转换为AES专用密钥 + return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM); + } +} \ No newline at end of file diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/AesDesDefaultEncrypt2.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/AesDesDefaultEncrypt2.java new file mode 100644 index 00000000..10283918 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/AesDesDefaultEncrypt2.java @@ -0,0 +1,87 @@ +package com.quick.db.crypt.encrypt; + +import com.quick.db.crypt.util.Hex; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +@Slf4j +public class AesDesDefaultEncrypt2 extends BaseEncrypt { + + + private static final String KEY_ALGORITHM = "AES"; + private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding"; + + private SecretKeySpec secretKeySpec; + + public AesDesDefaultEncrypt2() throws NoSuchAlgorithmException { + log.info("init encrypt by default passwd"); + this.password = getDefaultPassword(); + this.secretKeySpec = getSecretKey(this.password); + } + + public AesDesDefaultEncrypt2(String password) throws NoSuchAlgorithmException { + + if (StringUtils.isEmpty(password)) { + throw new IllegalArgumentException("password should not be null!"); + } + log.info("init encrypt by custom passwd"); + this.password = password; + this.secretKeySpec = getSecretKey(password); + } + + @Override + public String encrypt(String value) { + + try { + Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + + byte[] content = value.getBytes("UTF-8"); + byte[] encryptData = cipher.doFinal(content); + + return Hex.bytesToHexString(encryptData); + } catch (Exception e) { + log.error("AES加密时出现问题,密钥为:{}", password); + throw new IllegalStateException("AES加密时出现问题" + e.getMessage(), e); + } + } + + @Override + public String decrypt(String value) { + if (StringUtils.isEmpty(value)) { + return ""; + } + try { + byte[] encryptData = Hex.hexStringToBytes(value); + Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + byte[] content = cipher.doFinal(encryptData); + return new String(content, "UTF-8"); + } catch (Exception e) { + log.error("AES解密时出现问题,密钥为:{},密文为:{}", password, value); + throw new IllegalStateException("AES解密时出现问题" + e.getMessage(), e); + } + + } + + + private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException { + //返回生成指定算法密钥生成器的 KeyGenerator 对象 + KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM); + //AES 要求密钥长度为 128 + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + random.setSeed(password.getBytes()); + kg.init(128, random); + //生成一个密钥 + SecretKey secretKey = kg.generateKey(); + // 转换为AES专用密钥 + return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM); + } +} \ No newline at end of file diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/BaseEncrypt.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/BaseEncrypt.java new file mode 100644 index 00000000..d85cafa2 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/BaseEncrypt.java @@ -0,0 +1,29 @@ +package com.quick.db.crypt.encrypt; + +/** + * 继承此类,必须实现实现无参数和有参数的构造器 + */ +public abstract class BaseEncrypt implements Encrypt { + + private static final String DEFAULT_SEC = "FMjDV69Xkd6y9HVVK"; + + public String password; + + public String getDefaultPassword() { + return password; + } + + public BaseEncrypt() { + this.password = DEFAULT_SEC; + } + + public BaseEncrypt(String password) { + this.password = password; + } + + @Override + public abstract String encrypt(String src); + + @Override + public abstract String decrypt(String encrypt); +} \ No newline at end of file diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/Encrypt.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/Encrypt.java new file mode 100644 index 00000000..46a5848f --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/encrypt/Encrypt.java @@ -0,0 +1,21 @@ +package com.quick.db.crypt.encrypt; + +public interface Encrypt { + + + /** + * 对字符串进行加密存储 + * @param src 源 + * @return 返回加密后的密文 + * @throws RuntimeException 算法异常 + */ + String encrypt(String src); + + /** + * 对加密后的字符串进行解密 + * @param encrypt 加密后的字符串 + * @return 返回解密后的原文 + * @throws RuntimeException 算法异常 + */ + String decrypt(String encrypt); +} diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/intercept/CryptInterceptor.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/intercept/CryptInterceptor.java new file mode 100644 index 00000000..3fba8a73 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/intercept/CryptInterceptor.java @@ -0,0 +1,14 @@ +package com.quick.db.crypt.intercept; + +import com.quick.db.crypt.encrypt.Encrypt; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class CryptInterceptor { + + protected Encrypt encrypt; + + public CryptInterceptor(Encrypt encrypt) { + this.encrypt = encrypt; + } +} diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/intercept/CryptParamInterceptor.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/intercept/CryptParamInterceptor.java new file mode 100644 index 00000000..381445f3 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/intercept/CryptParamInterceptor.java @@ -0,0 +1,173 @@ +package com.quick.db.crypt.intercept; + +import com.quick.db.crypt.annotation.CryptEntity; +import com.quick.db.crypt.annotation.CryptField; +import com.quick.db.crypt.encrypt.Encrypt; +import com.quick.db.crypt.util.CryptInterceptorUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.binding.MapperMethod; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.plugin.*; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.springframework.util.CollectionUtils; + +import java.security.NoSuchAlgorithmException; +import java.sql.PreparedStatement; +import java.util.*; + +import static com.quick.db.crypt.util.CryptInterceptorUtil.ENTITY_FILED_ANN_MAP; + +@Intercepts({ + @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class) +}) +@Slf4j +public class CryptParamInterceptor extends CryptInterceptor implements Interceptor { + + /** + * org.apache.ibatis.reflection.ParamNameResolver#getNamedParams(java.lang.Object[]) + * 跳过无用的paramx + */ + private static final String GENERIC_NAME_PREFIX = "param"; + private static final String MAPPEDSTATEMENT = "mappedStatement"; + + + public CryptParamInterceptor(Encrypt encrypt) { + super(encrypt); + log.info("init CryptWriteInterceptor"); + } + + + @Override + public Object intercept(Invocation invocation) throws Throwable { + + ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget(); + MetaObject metaObject = SystemMetaObject.forObject(parameterHandler); + MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(MAPPEDSTATEMENT); + log.info("mappedStatement.getId(): {}", mappedStatement.getId()); + + Object params = metaObject.getValue("parameterObject"); + if (Objects.isNull(params)) { + return invocation.proceed(); + } + + /** + * params 单个参数 insert(user) Object + * params 集合参数 insert(list) MapperMethod$ParamMap + * https://github.com/miaoxinwei/mybatis-crypt/blob/master/src/main/java/org/apache/ibatis/plugin/CryptInterceptor.java + */ + log.info("params.getClass().getTypeName(): {}", params.getClass().getTypeName()); + + /** + * MapperMethod.ParamMap + * 参数使用@param,程序会自动增加对应的paramx + */ + if (params instanceof MapperMethod.ParamMap) { + MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) params; + + for (Map.Entry paramObj : paramMap.entrySet()) { + Object paramValue = paramObj.getValue(); + if (CryptInterceptorUtil.isNotCrypt(paramValue) || paramObj.getKey().contains(GENERIC_NAME_PREFIX)) { + continue; + } + + log.info("paramValue.getClass().getTypeName(): {}", paramValue.getClass().getTypeName()); + // 集合类型的参数 + if (paramValue instanceof Collection) { + listEntityCrypt((Collection) paramValue); + continue; + } + // 对象类型的参数 +// entityEncrypt(paramObj); + entityEncrypt(paramValue); + } + } else if (params instanceof Map) { + return invocation.proceed(); + } else { + // 走到这里一般代表方法中只有一个参数,并且米有添加@param注解 + log.warn("请检查方法中参数的写法,是否有加@param!!! 方法名为: {}", mappedStatement.getId()); + entityEncrypt(params); + } + return invocation.proceed(); + } + + + /** + * 对一个list进行加密处理 + * 暂时不考虑List 这种情况 + * + * @param paramList + * @throws Exception + */ + private void listEntityCrypt(Collection paramList) throws Exception { + Iterator iterator = paramList.iterator(); + while (iterator.hasNext()) { + Object next = iterator.next(); + if (CryptInterceptorUtil.isNotCrypt(next)) { + break; + } + entityEncrypt(next); + } + } + + + /** + * 对一个加了CryptEntity的对象中加了CryptField的注解进行加密处理 + * 进入到这里要么是一个对象实体,要么是map类型的对象实体 + * + * @param obj + * @throws Exception + */ + private void entityEncrypt(Object obj) throws Exception { + log.info("obj.getClass().getName(): {}", obj.getClass().getName()); + String objFqn = obj.getClass().getName(); + List objFieldList = ENTITY_FILED_ANN_MAP.getOrDefault(objFqn, new ArrayList<>()); + + MetaObject metaObject = SystemMetaObject.forObject(obj); + if (!CollectionUtils.isEmpty(objFieldList)) { + // 不为空,已缓存 + objFieldList.forEach(s -> cryptField(s, metaObject)); + } else { + // 为空,需要遍历 再缓存 + CryptEntity declaredAnnotation = obj.getClass().getDeclaredAnnotation(CryptEntity.class); + if (Objects.isNull(declaredAnnotation)) { + return; + } + Arrays.stream(obj.getClass().getDeclaredFields()) + .filter(item -> Objects.nonNull(item.getAnnotation(CryptField.class))) + .forEach(item -> { + String fieldName = item.getName(); + System.out.println(fieldName); + objFieldList.add(fieldName); + cryptField(fieldName, metaObject); + }); + ENTITY_FILED_ANN_MAP.put(objFqn, objFieldList); + } + + } + + private void cryptField(String fieldName, MetaObject metaObject) { + Object fieldVal = metaObject.getValue(fieldName); + if (Objects.isNull(fieldVal)) { + return; + } + if (fieldVal instanceof CharSequence) { + metaObject.setValue(fieldName, encrypt.encrypt(fieldVal.toString())); + } + } + + @Override + public Object plugin(Object target) { + if (target instanceof ParameterHandler) { + return Plugin.wrap(target, this); + } + return target; + } + + @Override + public void setProperties(Properties properties) { + + } +} \ No newline at end of file diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/intercept/CryptReadInterceptor.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/intercept/CryptReadInterceptor.java new file mode 100644 index 00000000..6e236d95 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/intercept/CryptReadInterceptor.java @@ -0,0 +1,105 @@ +package com.quick.db.crypt.intercept; + +import com.quick.db.crypt.annotation.CryptEntity; +import com.quick.db.crypt.annotation.CryptField; +import com.quick.db.crypt.encrypt.Encrypt; +import com.quick.db.crypt.util.PluginUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.plugin.*; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Properties; + +import static com.quick.db.crypt.util.CryptInterceptorUtil.ENTITY_FILED_ANN_MAP; + +@Intercepts({ + @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {java.sql.Statement.class}) +}) +@Slf4j +public class CryptReadInterceptor extends CryptInterceptor implements Interceptor { + + private static final String MAPPED_STATEMENT = "mappedStatement"; + + public CryptReadInterceptor(Encrypt encrypt) { + super(encrypt); + log.info("init CryptReadInterceptor"); + } + + @Override + public Object intercept(Invocation invocation) throws Throwable { + final List results = (List) invocation.proceed(); + if (results.isEmpty()) { + return results; + } + + final ResultSetHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); + final MetaObject metaObject = SystemMetaObject.forObject(statementHandler); + + final MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(MAPPED_STATEMENT); + final ResultMap resultMap = mappedStatement.getResultMaps().isEmpty() ? null : mappedStatement.getResultMaps().get(0); + + Object result = results.get(0); + + CryptEntity cryptEntity = result.getClass().getAnnotation(CryptEntity.class); + if (cryptEntity == null || resultMap == null) { + return results; + } + + List cryptFieldList = getCryptField(resultMap); + log.info("CryptReadInterceptor cryptFieldList: {}", cryptFieldList); + cryptFieldList.forEach(item -> { + results.forEach(x -> { + MetaObject objMetaObject = SystemMetaObject.forObject(x); + Object value = objMetaObject.getValue(item); + if (Objects.nonNull(value)) { + objMetaObject.setValue(item, encrypt.decrypt(value.toString())); + } + }); + }); + return results; + } + + /** + * 获取需要加密的属性 + * + * @param resultMap + * @return + */ + private List getCryptField(ResultMap resultMap) { + Class clazz = resultMap.getType(); + log.info("clazz: {}", clazz); + List fieldList = ENTITY_FILED_ANN_MAP.get(clazz.getName()); + if (Objects.isNull(fieldList)) { + fieldList = new ArrayList<>(); + for (Field declaredField : clazz.getDeclaredFields()) { + CryptField cryptField = declaredField.getAnnotation(CryptField.class); + if (cryptField != null) { + fieldList.add(declaredField.getName()); + } + } + ENTITY_FILED_ANN_MAP.put(clazz.getName(), fieldList); + } + return fieldList; + } + + + @Override + public Object plugin(Object target) { + if (target instanceof ResultSetHandler) { + return Plugin.wrap(target, this); + } + return target; + } + + @Override + public void setProperties(Properties properties) { + } +} \ No newline at end of file diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/CryptInterceptorUtil.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/CryptInterceptorUtil.java new file mode 100644 index 00000000..18858498 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/CryptInterceptorUtil.java @@ -0,0 +1,26 @@ +package com.quick.db.crypt.util; + +import org.apache.ibatis.mapping.SqlCommandType; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CryptInterceptorUtil { + + + /** + * 对象名称缓存> 对对象名和对象里的属性加解密的时候可以从缓存中直接读取 + */ + public static final ConcurrentHashMap> ENTITY_FILED_ANN_MAP = new ConcurrentHashMap<>(); + + + public static boolean isNotCrypt(Object o) { + return o == null || o instanceof Double || o instanceof Integer || o instanceof Long || o instanceof Boolean || o instanceof Map; + } + + public static boolean isWriteCmd(SqlCommandType commandType) { + return SqlCommandType.UPDATE.equals(commandType) || SqlCommandType.INSERT.equals(commandType); + + } +} \ No newline at end of file diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/Hex.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/Hex.java new file mode 100644 index 00000000..70467541 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/Hex.java @@ -0,0 +1,62 @@ +package com.quick.db.crypt.util; + +import org.springframework.util.StringUtils; + +public class Hex { + /** + * char 转 byte的适配器 + */ + private static final String BYTE_CONVERTE="0123456789ABCDEF"; + + /** + * 将byte转化为16进制的字符串 + * @param src byte[] 数组 + * @return 返回16进制字符串 + */ + public static String bytesToHexString(byte[] src){ + + if (src == null || src.length <= 0) { + return null; + } + + StringBuilder stringBuilder = new StringBuilder(""); + for (int j:src) { + int v = j & 0xFF; + String hv = Integer.toHexString(v); + if (hv.length() < 2) { + stringBuilder.append(0); + } + stringBuilder.append(hv); + } + return stringBuilder.toString(); + } + + /** + * 将16进制的字符串转化为byte[] 数组 + * @param hexString 16进制字符串 + * @return byte[] 数组 + */ + public static byte[] hexStringToBytes(String hexString) { + if (StringUtils.isEmpty(hexString)) { + return new byte[0]; + } + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); + } + return d; + } + + /** + * 将char转化为byte数组 + * @param c char + * @return byte + */ + private static byte charToByte(char c) { + return (byte) BYTE_CONVERTE.indexOf(c); + } +} diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/PluginUtils.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/PluginUtils.java new file mode 100644 index 00000000..52ea6554 --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/PluginUtils.java @@ -0,0 +1,25 @@ +package com.quick.db.crypt.util; + +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; + +import java.lang.reflect.Proxy; + +public final class PluginUtils { + + private PluginUtils() { + // to do nothing + } + + /** + * 获得真正的处理对象,可能多层代理. + */ + @SuppressWarnings("unchecked") + public static T realTarget(Object target) { + if (Proxy.isProxyClass(target.getClass())) { + MetaObject metaObject = SystemMetaObject.forObject(target); + return realTarget(metaObject.getValue("h.target")); + } + return (T) target; + } +} \ No newline at end of file diff --git a/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/TestMain.java b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/TestMain.java new file mode 100644 index 00000000..4d696d3a --- /dev/null +++ b/mybatis-crypt-plugin/src/main/java/com/quick/db/crypt/util/TestMain.java @@ -0,0 +1,13 @@ +package com.quick.db.crypt.util; + +import com.quick.db.crypt.encrypt.AesDesDefaultEncrypt; + +import java.security.NoSuchAlgorithmException; + +public class TestMain { + public static void main(String[] args) throws NoSuchAlgorithmException { + AesDesDefaultEncrypt aesDesDefaultEncrypt = new AesDesDefaultEncrypt(); +// System.out.println(aesDesDefaultEncrypt.decrypt("0e22e227f48d13e17baa500f68e72024")); + System.out.println(aesDesDefaultEncrypt.encrypt("17727826853")); + } +} \ No newline at end of file diff --git a/nacos-common-config/README.md b/nacos-common-config/README.md new file mode 100644 index 00000000..fa1f3cfa --- /dev/null +++ b/nacos-common-config/README.md @@ -0,0 +1,5 @@ +该模块只是quick-dubbo-nacos里的shareconfig依赖的bootstrap.yml + +抽取公共配置文件 + +验证时,最好不要在当前idea里验证,放在单独的idea里,mvn install 到本地repo中再由其他模块引入 \ No newline at end of file diff --git a/nacos-common-config/pom.xml b/nacos-common-config/pom.xml new file mode 100644 index 00000000..d36ee811 --- /dev/null +++ b/nacos-common-config/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + nacos-common-config + org.example + 1.0-SNAPSHOT + jar + + + 8 + 8 + + + \ No newline at end of file diff --git a/nacos-common-config/src/main/resources/bootstrap.yaml b/nacos-common-config/src/main/resources/bootstrap.yaml new file mode 100644 index 00000000..afc758f6 --- /dev/null +++ b/nacos-common-config/src/main/resources/bootstrap.yaml @@ -0,0 +1,33 @@ +spring: + application: + name: nacos-config-share + cloud: + nacos: + discovery: + server-addr: ${nacos.address} + config: + server-addr: ${nacos.address} + prefix: ${spring.application.name} + file-extension: yml + shared-configs[0]: + data-id: application-common-redis.yml # 配置文件名-Data Id + refresh: true # 是否动态刷新,默认为false + shared-configs[1]: + data-id: server-common.yml # 配置文件名-Data Id + refresh: true # 是否动态刷新,默认为false + +# -Dspring.profiles.active=idea +--- +# +spring: + profiles: container +nacos: + address: nacos-standalone-mysql:8848 + +--- +spring: + profiles: idea +nacos: + address: localhost:8848 +# +# diff --git a/pom.xml b/pom.xml index f5ac137a..ade1dedc 100644 --- a/pom.xml +++ b/pom.xml @@ -8,23 +8,19 @@ spring-boot-quick 1.0-SNAPSHOT + + quick-platform + quick-framework quick-swagger - quick-thread - quick-crawler quick-modules - quick-spark quick-package-assembly - quick-tika - quick-ElasticSearch quick-img2txt quick-batch quick-rabbitmq - quick-simhash quick-multi-data quick-package-assembly-multi-env quick-exception quick-log - quick-wx-api quick-oss quick-jsp quick-mybatis-druid @@ -32,7 +28,63 @@ quick-okhttp quick-redies quick-oauth2 + quick-jpa + quick-rocketmq + quick-logback + quick-activemq + quick-container + quick-vue + quick-dynamic-bean + quick-activemq2 + quick-dubbo + quick-async + quick-vw-crawler + quick-cache + quick-feign + quick-config-encrypt + quick-jwt + quick-wx-public + quick-lombok + quick-monitor-thread + quick-rest-template + quick-multi-rabbitmq + quick-kafka + quick-hbase + quick-starter + quick-starter-demo + quick-mongodb + quick-method-evaluate + quick-spring-shiro + quick-config-server + quick-i18n + quick-undertow + quick-mail + quick-graphQL + quick-saturn + quick-zookeeper + quick-app + quick-profiles + quick-hmac + quick-dubbo-nacos + nacos-common-config + mybatis-crypt-plugin + mybatis-crypt-plugin-test + quick-shiro + quick-camunda + quick-flowable + quick-sharding-jdbc + quick-shiro-cas + quick-maven-plugin + quick-sse + quick-multi-api-invoker + quick-abstract-template + quick-abstract-template2 + quick-api-invoker + quick-platform-component + quick-platform-common + quick-sample-server pom 使用springboot框架做的一些例子,做个记录,以后方便即拿即用 - \ No newline at end of file + + diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/Application.java b/quick-ElasticSearch/src/main/java/com/quick/es/Application.java deleted file mode 100644 index 8ee76cf6..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/Application.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.quick.es; - -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * Created by Administrator on 2017/6/5 0005. - */ -@SpringBootApplication -public class Application implements CommandLineRunner { - - @Override - public void run(String... strings) throws Exception { - } - - public static void main(String[] args) - { - SpringApplication.run(Application.class,args); - } - -} diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/bulk/BulkMain.java b/quick-ElasticSearch/src/main/java/com/quick/es/bulk/BulkMain.java deleted file mode 100644 index bf792d82..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/bulk/BulkMain.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.quick.es.bulk; - -import com.quick.es.config.ESClientConfig; -import org.elasticsearch.action.bulk.BulkRequestBuilder; -import org.elasticsearch.action.bulk.BulkResponse; -import org.elasticsearch.client.transport.TransportClient; - -import java.io.IOException; -import java.net.UnknownHostException; -import java.util.Date; - -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; - -/** - * Created with IDEA - * User: vector - * Data: 2017/6/6 - * Time: 19:34 - * Description: - */ -public class BulkMain { - - - public static void main(String[] args) { - - try { - TransportClient client = ESClientConfig.getClient(); - BulkRequestBuilder bulkRequest = client.prepareBulk(); - - // either use client#prepare, or use Requests# to directly build index/delete requests - bulkRequest.add(client.prepareIndex("twitter", "tweet", "1") - .setSource(jsonBuilder() - .startObject() - .field("user", "kimchy") - .field("postDate", new Date()) - .field("message", "trying out Elasticsearch") - .endObject() - ) - ); - - bulkRequest.add(client.prepareIndex("twitter", "tweet", "2") - .setSource(jsonBuilder() - .startObject() - .field("user", "kimchy") - .field("postDate", new Date()) - .field("message", "another post") - .endObject() - ) - ); - - BulkResponse bulkResponse = bulkRequest.get(); - if (bulkResponse.hasFailures()) { - // process failures by iterating through each bulk response item - } - } catch (UnknownHostException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/client/EsClient.java b/quick-ElasticSearch/src/main/java/com/quick/es/client/EsClient.java deleted file mode 100644 index c6bb6397..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/client/EsClient.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.quick.es.client; - - -import com.quick.es.config.ESClientConfig; -import com.quick.es.util.DataFactory; -import org.elasticsearch.action.bulk.BulkRequestBuilder; -import org.elasticsearch.action.bulk.BulkResponse; -import org.elasticsearch.action.delete.DeleteResponse; -import org.elasticsearch.action.index.IndexResponse; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.client.transport.TransportClient; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.script.Script; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHits; -import org.elasticsearch.search.sort.ScriptSortBuilder; -import org.elasticsearch.search.sort.SortBuilders; - -import java.io.IOException; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.ExecutionException; - -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; - -public class EsClient { - - public final static String INDEX_TEST = "yzresume_test"; - public final static String TYPE_TEST = "yzresume_test"; - - public final static String INDEX = "data_quality_resume"; - public final static String TYPE = "yzresume"; - - public static void main(String[] args) { - try { -// createCluterName(INDEX); - // TODO -// createMapping(); -// createIndex(); - search(); -// putDocument(); -// deleteDocument(); - - - - } catch (Exception e) { - e.printStackTrace(); - } - } - - private static void deleteDocument() throws UnknownHostException, ExecutionException, InterruptedException { - TransportClient client = ESClientConfig.getClient(); - DeleteResponse deleteResponse = client.prepareDelete(INDEX_TEST, TYPE_TEST, "a79a03d9-a4f3-4b94-9c62-579c9777d853").execute().get(); - System.out.println(deleteResponse.status().getStatus()); - } - - private static void putDocument() throws IOException { - - - TransportClient client = ESClientConfig.getClient(); - BulkRequestBuilder bulkRequest = client.prepareBulk(); - bulkRequest.add(client.prepareIndex(INDEX_TEST, TYPE_TEST) - .setSource(jsonBuilder() - .startObject() - .field("candidate_id", "1283901280938120938210938") - .field("company", "八爪网络") - .field("id", "sdgdfsgdfgdsf") - .field("location_id", "432") - .field("mobile", "18255062124") - .field("owner_id", "jalkdjfklasjfla;sjfkl;sd") - .field("real_name", "mrs down") - .field("source_updated", new Date()) - .field("title", "java") - .field("updated", new Date()) - .field("year_of_experience", 2) - .endObject()) - ); - BulkResponse bulkResponse = bulkRequest.get(); - if (bulkResponse.hasFailures()) { - // process failures by iterating through each bulk response item - System.out.println("插入失败"); - } - - } - - private static void createMapping() { - XContentBuilder mapping = null; - try { - mapping = jsonBuilder() - .startObject() - .field("yzresume") - .startObject() - .field("properties") - .startObject() - .field("company").startObject().field("type", "text").endObject() - .field("id").startObject().field("type", "keyword").endObject() - .field("location_id").startObject().field("type", "long").endObject() - .field("mobile").startObject().field("type", "keyword").endObject() - .field("owner_id").startObject().field("type", "text").endObject() - .field("real_name").startObject().field("type", "string").endObject() - .field("year_of_experience").startObject().field("type", "integer").endObject() - .field("title").startObject().field("type", "text").endObject() - .field("updated").startObject().field("type", "date").endObject() - .field("source_updated").startObject().field("type", "date").endObject() - .field("candidate_id").startObject().field("type", "keyword").endObject() - .endObject() - .endObject() - .endObject(); - - System.out.println(mapping.string()); - -// PutMappingRequest source = Requests.putMappingRequest(INDEX).type(TYPE).source(mapping); -// TransportClient client = ESClientConfig.getClient(); -// client.admin().indices().preparePutMapping(INDEX) -// .setType(TYPE).setSource(mapping).get(); -// client.close(); - - } catch (IOException e) { - e.printStackTrace(); - } - - - } - - - public static List getSearchResult(String title,String company,int location_id,int length) throws UnknownHostException { - List result = new ArrayList<>(); - SearchResponse scrollResp = ESClientConfig.getClient() - .prepareSearch("data_quality_resume") - .setTypes("resume") // - .setQuery(QueryBuilders.boolQuery() - .must(QueryBuilders.matchQuery("title", title)) - .must(QueryBuilders.termQuery("location_id", 432))) - .addSort(SortBuilders.scriptSort(new Script("Math.random()"), ScriptSortBuilder.ScriptSortType.NUMBER)) - .setSize(length).get(); - SearchHits searchHits = scrollResp.getHits(); - for(SearchHit item:searchHits){ - result.add(item.getSourceAsString()); - } - return result; - } - - public static void createCluterName(String indices) { - TransportClient client = null; - try { - client = ESClientConfig.getClient(); - } catch (UnknownHostException e) { - e.printStackTrace(); - } - client.admin().indices().prepareCreate(indices).execute().actionGet(); - client.close(); - } - - private static void search() throws UnknownHostException { - TransportClient client = ESClientConfig.getClient(); - - -// GetResponse response = client.prepareGet("world", "city", "AVx9ZrO1r3OKqmuL07ZS") -// .setOperationThreaded(false) -// .get(); -// System.out.println(response.getSourceAsString()); - -// QueryBuilder qb = QueryBuilders.matchAllQuery();// 全部 -// QueryBuilder qb = QueryBuilders.matchQuery("name", "ar"); -// QueryBuilder qb = QueryBuilders.multiMatchQuery("herat","name","district"); // OR 多个字段 -// QueryBuilder qb = QueryBuilders.rangeQuery("population").gte(100000).lt(250000); // RANGER -// QueryBuilder qb = QueryBuilders.termQuery("name","herat").boost(10);// 准确 -// QueryBuilder qb = QueryBuilders.fuzzyQuery("name","ar");// 模糊查询 -// QueryBuilder qb = QueryBuilders.termsQuery(); // 含有多个词条 - -// QueryBuilder qb = QueryBuilders.matchQuery("name","ar").operator(Operator.AND); -// QueryBuilder qb1 = QueryBuilders.matchQuery("name", "Qandahar"); - // .must(QueryBuilders.termQuery("title","android")) -// .mustNot(QueryBuilders.fuzzyQuery("location","广东")) -// .must(QueryBuilders.matchQuery("degree","本科")) -// .mustNot(QueryBuilders.matchQuery("company","腾讯"))// 不在这家公司 -// .mustNot(QueryBuilders.matchQuery("id","9F5AE922-344A-4036-84EF-ADB45292A2B7")) -// .mustNot(QueryBuilders.matchQuery("id","a55d0234-3a76-4962-bab6-0b563e81ac1a")) - /*.must(QueryBuilders.rangeQuery("yearofexperience").gte(3).lte(5))*/ - System.out.println(client.nodeName()); - long start = System.currentTimeMillis(); - -// String[] fid = {"a7990254-20c1-468a-a196-3d15beed1f70","a79a0444-ce18-4b90-acb4-3b0897fa2982","a79c0384-6eb4-47c6-96f7-6fed5f95847b","a79a03cc-7588-4d3c-ba79-a4149cddde83", -// "a79203e6-f288-4de0-b454-bc887b895991","a79203e6-dae5-4971-898b-fd1c5303a1f1","a79203e6-f111-465e-b6c7-aad80bf09af9"}; -// BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); -// queryBuilder -// .must(QueryBuilders.matchQuery("title", "cto")) -// .mustNot(QueryBuilders.matchQuery("owner_id","a7850376-9bf7-4ad0-9bfd-997806dac036")); -//// -// List strings = Arrays.asList(fid); -// strings.forEach(t->queryBuilder.mustNot(QueryBuilders.matchQuery("talent_id",t))); - -// QueryBuilders.boolQuery().should(QueryBuilders.matchPhraseQuery("title", "产品经理").slop(2)) -// .should(QueryBuilders.matchQuery("title", "java")) -// .should(QueryBuilders.matchQuery("title", "android")); - -// BoolQueryBuilder mobile = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("mobile", "13072130095"); - - // QueryBuilders.matchPhraseQuery("title","产品经理").slop(5) - -// MatchPhraseQueryBuilder slop = QueryBuilders.matchPhraseQuery("title", "技术总监").slop(2); - - SearchResponse scrollResp1 = client - .prepareSearch(INDEX) - .setTypes(TYPE) // - .setQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchPhraseQuery("title", "产品设计").slop(2))) - .setSize(100) - .addSort(SortBuilders.scriptSort(new Script("Math.random()"), ScriptSortBuilder.ScriptSortType.NUMBER)) - .get(); - - - - SearchHits searchHits1 = scrollResp1.getHits(); - long end = System.currentTimeMillis(); - //共搜到:" + searchHits.getTotalHits() + "条结果!共 - System.out.println("耗时" + (double) (end - start) / 1000 + "s。"); - //遍历结果 - for (SearchHit hit : searchHits1) { - System.out.println(hit.getSourceAsString()); - } - - } - - private static void createIndex() throws UnknownHostException { - TransportClient client = ESClientConfig.getClient(); - - // 创建索引和文档 - List jsonData = DataFactory.getInitJsonData(); - for (int i = 0; i < jsonData.size(); i++) { - IndexResponse indexResponse = client.prepareIndex("world", "city").setSource(jsonData.get(i)).get(); - System.out.println(indexResponse.status().name()); - } -// GetResponse response = client.prepareGet("testcase", "candidates", "00176455-5a00-48b0-9e8d-a6a3011d470d").execute().actionGet(); -// System.out.println(response.getSourceAsString()); - client.close(); - - String str = "\u000B更新时间:2016-01-25 范大伟 3-4年工作经验 | 男 | 25岁(1990年4月2日) | 未婚 | 177cm | 中共党员 \u000B(ID:300353072) 居住地: 上海-浦东新区 电 话: 13817366531(手机) E-mail: 402286552@qq.com \u0014402286552@qq.com\u0015 最近工作 [ 1 年] 公 司: 加拿大KFS国际建筑师事务所 行 业: 建筑/建材/工程 职 位: 市场总监 学历 学 历: 本科 专 业: 工程管理 学 校: 同济大学 目前薪资: 年薪 15-20万 人民币 自我评价 从工作伊始,专注于建筑行业市场方面工作,参与执行了集团公司旗下新公司的成立、组建、开业、市场调研、业务拓展全过程。对团队的组建有一定的心得体会,从一个人做市场起步,到成功组建功能齐备的市场部和销售部,熟悉各个环节之间的关联和流程。带领团队研究出符合市场需求的方案;根据市场调研反馈及时调整产品的价格。建立公司网站,在工作中与各大主流媒体以及公关公司保持良好的关系。英语口语流利,和外籍同事、客户交流无障碍。 求职意向 到岗时间: 一周以内 工作性质: 全职 希望行业: 建筑/建材/工程,房地产 目标地点: 上海 期望薪资: 月薪 10000-14999 目标职能: 市场/营销/拓展经理,商务经理,大客户管理 求职状态: 目前正在找工作 工作经验 2015 /1--至今:加拿大KFS国际建筑师事务所(少于50人) [ 1 年] 所属行业: 建筑/建材/工程 市场部 市场总监 1.制定年度市场营销计划和年度预算;\u000B2.制定品牌推广策略并落实;\u000B3.拓展新项目及维护客户关系;\u000B4.沟通协调项目进度;\u000B5.结合公司品牌推广和项目,策划并执行了两部关于建筑师的音乐剧:《河谷三号的故事》、《寻找安贝尔》,在各大剧院上演。 2014 /6--2015 /1:悉地国际(1000-5000人) [ 7个月] 所属行业: 建筑/建材/工程 公共建筑事业部-都市综合体子产品管理部 产品业务拓展经理 1.开拓新项目及维护重要的客户关系;\u000B2.建立大客户体系并进行有效管理;\u000B3.建立子产品分类体系,对不同类别的客户提供有针对性的应对措施;\u000B4.分析子产品经营数据,掌握市场动态;\u000B5.与区域和城市市场团队及资源团队密切配合,促使项目的落实;\u000B6.配合副总裁对公建项目及设计团队进行协调和管理。 2013 /8--2014 /6:上海方大建筑设计事务所(150-500人) [ 10个月] 所属行业: 建筑/建材/工程 市场部 区域市场经理 1.负责项目的商务洽谈及合同落实;\u000B2.负责公司集团客户及重大客户(万科、德信地产、正大、中梁集团等)的关系维护及项目的深度挖掘;\u000B3.所负责项目的回款工作;\u000B4.与设计师密切联系,与开发商进行沟通;\u000B5.兼管公司的品牌推广与宣传。 2012 /2--2013 /7:上海金汤旺河文化发展有限公司(150-500人) [ 1 年5个月] 所属行业: 房地产 市场部 市场部经理 负责集团旗下亚欧巄森林城堡乐园的整体市场计划和拓展; \u000B包括销售资料开发、公司网站创建以及销售的管理; \u000B与各大合作伙伴,商会、协会建立良好的合作关系;\u000B与总裁共同开展旗下旅游地产的营销策划;\u000B从公司筹建到正式运营全程参与并担任重要角色。 汇报对象: 总裁 下属人数: 12 证 明 人: Vicky Jia 工作业绩: 带领团队根据市场情况开发销售资料,包括brochure、三折页、名片设计,建立网站以及销售ppt的制作;\u000B研究产品,根据市场行情作出合理的产品价格调整,并及时向各地区销售人员公布;\u000B帮助销售团队与各大合作伙伴建立良好的合作关系;\u000B策划乐园的第一届跨国夏季森林音乐会暨开幕仪式,为乐园的宣传造势。 教育经历 2013 /12--至今 同济大学 工程管理 本科 2009 /9--2012 /6 上海第二工业大学 建筑经济管理 大专 主要包括会计、建筑CAD、金融学、管理学、房屋构造、建筑材料、工程招标与合同管理、市场营销、工程造价的确定与控制、建筑工程项目管理、建筑工程概预算等课程。了解房地产行业与建筑行业的知识以及营销等经济知识。\u000B在校期间连续三年获得校级奖学金,并被评为校级优秀学生干部。 培训经历 2014 /2--2014 /6: EMBA总裁班教授戴剑老师 EMBA市场营销培训 语言能力 英语(熟练): 听说(熟练),读写(熟练) 英语等级: 英语四级 FanDavid 3-4 years| Male | 25 Years old(1990/4/2) | Unmarried | 177cm | Party Member \u000B(ID:300353072) Residency: Shanghai-Pudong Telephone number: 13817366531(Mobile Phone) E-mail: 402286552@qq.com \u0014402286552@qq.com\u0015 The latest work [ 1 year] Company: K. F. Stone Design International Inc. Canada Industry: Architectural Services/Building Materials/Construction Job Title: Marketing Director Education Degree: Bachelor Major: Engineering Management School: Tongji University Current Salary: Annual Salary 150,000-200,000 RMB Self Assessment Involved in executing the whole program of founding,structuring,opening,marketing investigation and business expanding of new company subsidiary to the Group Company.\u000BExpertise in forming a team and familiar with the inner operation and the association about marketing and sales.\u000BLeader in a team to research the programs that face up to markets and modulate the price in a flexible and timely manner.\u000BKeep a good communication with the main media and Public relations firm.\u000BFluent in English. Career Objective I can start: within 1 week Employment Type: Full-time Desired Industry: Architectural Services/Building Materials/Construction,Real Estate Desired Location: Shanghai Desired Salary: Monthly Salary 10,000-14,999 Desired Position: Marketing / BD Manager,Business Manager,Key Account Management Current Situation: I'm looking for jobs now Work Experience 2015/1--Present:K. F. Stone Design International Inc. Canada(<50 people) [ 1 year] Industry: Architectural Services/Building Materials/Construction Marketing Department Marketing Director 1.Set up the annual marketing plan and budget.\u000B2.Achieve the contract value target set by company.\u000B3.Develop and maintain the relationship with key accounts.\u000B4.Develop relationship with project management firms, design firms, LDI and other business partners.\u000B5.Attend marketing events to pull through business opportunities. 2014/6--2015/1:CCDI Group(5000-10000 people) [ 7month] Industry: Architectural Services/Building Materials/Construction Management Department of HOPSCA Business Develop Manager 1.Develop new project and keep available the relationship with key clients;\u000B2.Analyze the marketing data of sub-productions,focus on watching the marketing trends;\u000B3.Supply the professional assistance to the operators;\u000B4.Classify the sub-productions and offer relative measures to different requirements of clients;\u000B5.Work with the team of marketing and sources of other regions and cities to promote the implementation of the project. 2013/8--2014/6:Shanghai Fangda Architecture Design Co.,Ltd(150-500 people) [ 10month] Industry: Architectural Services/Building Materials/Construction Marketing Department Regional Marketing Manager 1.Be responsible for business negotiations and settling contract;\u000B2.Maintain a sound and good relationship with the key clients and tract the potential projects;\u000B3.Check out the payment of the projects;\u000B4.Communicate with architect and Property Developers closely;\u000B5.Currently in charge of the promotion and publicity of the brands. 2012/2--2013/7:Shanghai Jintangwang Culture Development Co.,Ltd(150-500 people) [ 1 year and 5month] Industry: Real Estate Marketing Department Marketing Manager 1.Take charge of the market planning and development of Tourist Real Estate;\u000B2.Develop and manage the sales resources & set up company website and partnership with Association of Home Builders,etc;\u000B3.Work with President together to carry out the marketing planning. Education 2013/12--Present Tongji University Engineering Management Bachelor 2009/9--2012/6 Shanghai Second Polytechnic University Construction Economics and Management Associate Training 2014/2--2014/6: Professor Dai Jian of CEIBS EMBA Marketing Training Language Skills English(Very Good ): Listening&Speaking(Very Good),Reading&Writing(Very Good) English Grade: CET 4 \f"; - } - - -} diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/config/ESClientConfig.java b/quick-ElasticSearch/src/main/java/com/quick/es/config/ESClientConfig.java deleted file mode 100644 index cfef35db..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/config/ESClientConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.quick.es.config; - -import org.elasticsearch.client.transport.TransportClient; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.InetSocketTransportAddress; -import org.elasticsearch.transport.client.PreBuiltTransportClient; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - * Created with IDEA - * User: vector - * Data: 2017/6/6 - * Time: 19:35 - * Description: - */ -public class ESClientConfig { - - private static TransportClient transportClient; - - public static TransportClient getClient() throws UnknownHostException { - if(transportClient==null){ - Settings settings = Settings.builder() - .put("cluster.name", "data_es").build(); - transportClient = new PreBuiltTransportClient(settings) - .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("120.55.168.18"), 8830)); - - } - return transportClient; - } -} diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/entity/City.java b/quick-ElasticSearch/src/main/java/com/quick/es/entity/City.java deleted file mode 100644 index 4b00cd38..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/entity/City.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.quick.es.entity; - -public class City { - private Integer id; - - private String name; - - private String countrycode; - - private String district; - - private Integer population; - - public City(Integer id, String name, String countrycode, String district, Integer population) { - this.id = id; - this.name = name; - this.countrycode = countrycode; - this.district = district; - this.population = population; - } - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name == null ? null : name.trim(); - } - - public String getCountrycode() { - return countrycode; - } - - public void setCountrycode(String countrycode) { - this.countrycode = countrycode == null ? null : countrycode.trim(); - } - - public String getDistrict() { - return district; - } - - public void setDistrict(String district) { - this.district = district == null ? null : district.trim(); - } - - public Integer getPopulation() { - return population; - } - - public void setPopulation(Integer population) { - this.population = population; - } -} \ No newline at end of file diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/entity/CityExample.java b/quick-ElasticSearch/src/main/java/com/quick/es/entity/CityExample.java deleted file mode 100644 index a2dd1454..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/entity/CityExample.java +++ /dev/null @@ -1,530 +0,0 @@ -package com.quick.es.entity; - -import java.util.ArrayList; -import java.util.List; - -public class CityExample { - protected String orderByClause; - - protected boolean distinct; - - protected List oredCriteria; - - public CityExample() { - oredCriteria = new ArrayList(); - } - - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - - public String getOrderByClause() { - return orderByClause; - } - - public void setDistinct(boolean distinct) { - this.distinct = distinct; - } - - public boolean isDistinct() { - return distinct; - } - - public List getOredCriteria() { - return oredCriteria; - } - - public void or(Criteria criteria) { - oredCriteria.add(criteria); - } - - public Criteria or() { - Criteria criteria = createCriteriaInternal(); - oredCriteria.add(criteria); - return criteria; - } - - public Criteria createCriteria() { - Criteria criteria = createCriteriaInternal(); - if (oredCriteria.size() == 0) { - oredCriteria.add(criteria); - } - return criteria; - } - - protected Criteria createCriteriaInternal() { - Criteria criteria = new Criteria(); - return criteria; - } - - public void clear() { - oredCriteria.clear(); - orderByClause = null; - distinct = false; - } - - protected abstract static class GeneratedCriteria { - protected List criteria; - - protected GeneratedCriteria() { - super(); - criteria = new ArrayList(); - } - - public boolean isValid() { - return criteria.size() > 0; - } - - public List getAllCriteria() { - return criteria; - } - - public List getCriteria() { - return criteria; - } - - protected void addCriterion(String condition) { - if (condition == null) { - throw new RuntimeException("Value for condition cannot be null"); - } - criteria.add(new Criterion(condition)); - } - - protected void addCriterion(String condition, Object value, String property) { - if (value == null) { - throw new RuntimeException("Value for " + property + " cannot be null"); - } - criteria.add(new Criterion(condition, value)); - } - - protected void addCriterion(String condition, Object value1, Object value2, String property) { - if (value1 == null || value2 == null) { - throw new RuntimeException("Between values for " + property + " cannot be null"); - } - criteria.add(new Criterion(condition, value1, value2)); - } - - public Criteria andIdIsNull() { - addCriterion("ID is null"); - return (Criteria) this; - } - - public Criteria andIdIsNotNull() { - addCriterion("ID is not null"); - return (Criteria) this; - } - - public Criteria andIdEqualTo(Integer value) { - addCriterion("ID =", value, "id"); - return (Criteria) this; - } - - public Criteria andIdNotEqualTo(Integer value) { - addCriterion("ID <>", value, "id"); - return (Criteria) this; - } - - public Criteria andIdGreaterThan(Integer value) { - addCriterion("ID >", value, "id"); - return (Criteria) this; - } - - public Criteria andIdGreaterThanOrEqualTo(Integer value) { - addCriterion("ID >=", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLessThan(Integer value) { - addCriterion("ID <", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLessThanOrEqualTo(Integer value) { - addCriterion("ID <=", value, "id"); - return (Criteria) this; - } - - public Criteria andIdIn(List values) { - addCriterion("ID in", values, "id"); - return (Criteria) this; - } - - public Criteria andIdNotIn(List values) { - addCriterion("ID not in", values, "id"); - return (Criteria) this; - } - - public Criteria andIdBetween(Integer value1, Integer value2) { - addCriterion("ID between", value1, value2, "id"); - return (Criteria) this; - } - - public Criteria andIdNotBetween(Integer value1, Integer value2) { - addCriterion("ID not between", value1, value2, "id"); - return (Criteria) this; - } - - public Criteria andNameIsNull() { - addCriterion("Name is null"); - return (Criteria) this; - } - - public Criteria andNameIsNotNull() { - addCriterion("Name is not null"); - return (Criteria) this; - } - - public Criteria andNameEqualTo(String value) { - addCriterion("Name =", value, "name"); - return (Criteria) this; - } - - public Criteria andNameNotEqualTo(String value) { - addCriterion("Name <>", value, "name"); - return (Criteria) this; - } - - public Criteria andNameGreaterThan(String value) { - addCriterion("Name >", value, "name"); - return (Criteria) this; - } - - public Criteria andNameGreaterThanOrEqualTo(String value) { - addCriterion("Name >=", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLessThan(String value) { - addCriterion("Name <", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLessThanOrEqualTo(String value) { - addCriterion("Name <=", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLike(String value) { - addCriterion("Name like", value, "name"); - return (Criteria) this; - } - - public Criteria andNameNotLike(String value) { - addCriterion("Name not like", value, "name"); - return (Criteria) this; - } - - public Criteria andNameIn(List values) { - addCriterion("Name in", values, "name"); - return (Criteria) this; - } - - public Criteria andNameNotIn(List values) { - addCriterion("Name not in", values, "name"); - return (Criteria) this; - } - - public Criteria andNameBetween(String value1, String value2) { - addCriterion("Name between", value1, value2, "name"); - return (Criteria) this; - } - - public Criteria andNameNotBetween(String value1, String value2) { - addCriterion("Name not between", value1, value2, "name"); - return (Criteria) this; - } - - public Criteria andCountrycodeIsNull() { - addCriterion("CountryCode is null"); - return (Criteria) this; - } - - public Criteria andCountrycodeIsNotNull() { - addCriterion("CountryCode is not null"); - return (Criteria) this; - } - - public Criteria andCountrycodeEqualTo(String value) { - addCriterion("CountryCode =", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeNotEqualTo(String value) { - addCriterion("CountryCode <>", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeGreaterThan(String value) { - addCriterion("CountryCode >", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeGreaterThanOrEqualTo(String value) { - addCriterion("CountryCode >=", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeLessThan(String value) { - addCriterion("CountryCode <", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeLessThanOrEqualTo(String value) { - addCriterion("CountryCode <=", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeLike(String value) { - addCriterion("CountryCode like", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeNotLike(String value) { - addCriterion("CountryCode not like", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeIn(List values) { - addCriterion("CountryCode in", values, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeNotIn(List values) { - addCriterion("CountryCode not in", values, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeBetween(String value1, String value2) { - addCriterion("CountryCode between", value1, value2, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeNotBetween(String value1, String value2) { - addCriterion("CountryCode not between", value1, value2, "countrycode"); - return (Criteria) this; - } - - public Criteria andDistrictIsNull() { - addCriterion("District is null"); - return (Criteria) this; - } - - public Criteria andDistrictIsNotNull() { - addCriterion("District is not null"); - return (Criteria) this; - } - - public Criteria andDistrictEqualTo(String value) { - addCriterion("District =", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictNotEqualTo(String value) { - addCriterion("District <>", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictGreaterThan(String value) { - addCriterion("District >", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictGreaterThanOrEqualTo(String value) { - addCriterion("District >=", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictLessThan(String value) { - addCriterion("District <", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictLessThanOrEqualTo(String value) { - addCriterion("District <=", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictLike(String value) { - addCriterion("District like", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictNotLike(String value) { - addCriterion("District not like", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictIn(List values) { - addCriterion("District in", values, "district"); - return (Criteria) this; - } - - public Criteria andDistrictNotIn(List values) { - addCriterion("District not in", values, "district"); - return (Criteria) this; - } - - public Criteria andDistrictBetween(String value1, String value2) { - addCriterion("District between", value1, value2, "district"); - return (Criteria) this; - } - - public Criteria andDistrictNotBetween(String value1, String value2) { - addCriterion("District not between", value1, value2, "district"); - return (Criteria) this; - } - - public Criteria andPopulationIsNull() { - addCriterion("Population is null"); - return (Criteria) this; - } - - public Criteria andPopulationIsNotNull() { - addCriterion("Population is not null"); - return (Criteria) this; - } - - public Criteria andPopulationEqualTo(Integer value) { - addCriterion("Population =", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationNotEqualTo(Integer value) { - addCriterion("Population <>", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationGreaterThan(Integer value) { - addCriterion("Population >", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationGreaterThanOrEqualTo(Integer value) { - addCriterion("Population >=", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationLessThan(Integer value) { - addCriterion("Population <", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationLessThanOrEqualTo(Integer value) { - addCriterion("Population <=", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationIn(List values) { - addCriterion("Population in", values, "population"); - return (Criteria) this; - } - - public Criteria andPopulationNotIn(List values) { - addCriterion("Population not in", values, "population"); - return (Criteria) this; - } - - public Criteria andPopulationBetween(Integer value1, Integer value2) { - addCriterion("Population between", value1, value2, "population"); - return (Criteria) this; - } - - public Criteria andPopulationNotBetween(Integer value1, Integer value2) { - addCriterion("Population not between", value1, value2, "population"); - return (Criteria) this; - } - } - - public static class Criteria extends GeneratedCriteria { - - protected Criteria() { - super(); - } - } - - public static class Criterion { - private String condition; - - private Object value; - - private Object secondValue; - - private boolean noValue; - - private boolean singleValue; - - private boolean betweenValue; - - private boolean listValue; - - private String typeHandler; - - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - - protected Criterion(String condition) { - super(); - this.condition = condition; - this.typeHandler = null; - this.noValue = true; - } - - protected Criterion(String condition, Object value, String typeHandler) { - super(); - this.condition = condition; - this.value = value; - this.typeHandler = typeHandler; - if (value instanceof List) { - this.listValue = true; - } else { - this.singleValue = true; - } - } - - protected Criterion(String condition, Object value) { - this(condition, value, null); - } - - protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { - super(); - this.condition = condition; - this.value = value; - this.secondValue = secondValue; - this.typeHandler = typeHandler; - this.betweenValue = true; - } - - protected Criterion(String condition, Object value, Object secondValue) { - this(condition, value, secondValue, null); - } - } -} \ No newline at end of file diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/entity/IndustryPosition.java b/quick-ElasticSearch/src/main/java/com/quick/es/entity/IndustryPosition.java deleted file mode 100644 index 7841ec7f..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/entity/IndustryPosition.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.quick.es.entity; - -/** - * Created with IDEA - * User: vector - * Data: 2017/6/8 - * Time: 16:16 - * Description: - */ -public class IndustryPosition { - private String industry; // 行业 - private String position; // 职位 - - public IndustryPosition(String industry, String position) { - this.industry = industry; - this.position = position; - } - - @Override - public String toString() { - return "IndustryPosition{" + - "industry='" + industry + '\'' + - ", position='" + position + '\'' + - '}'; - } - - public String getIndustry() { - return industry; - } - - public void setIndustry(String industry) { - this.industry = industry; - } - - public String getPosition() { - return position; - } - - public void setPosition(String position) { - this.position = position; - } -} diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/mapper/CityMapper.java b/quick-ElasticSearch/src/main/java/com/quick/es/mapper/CityMapper.java deleted file mode 100644 index e657a2c2..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/mapper/CityMapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.quick.es.mapper; - -import com.quick.es.entity.City; -import com.quick.es.entity.CityExample; -import java.util.List; -import org.apache.ibatis.annotations.Param; - -public interface CityMapper { - int countByExample(CityExample example); - - int deleteByExample(CityExample example); - - int deleteByPrimaryKey(Integer id); - - int insert(City record); - - int insertSelective(City record); - - List selectByExample(CityExample example); - - City selectByPrimaryKey(Integer id); - - int updateByExampleSelective(@Param("record") City record, @Param("example") CityExample example); - - int updateByExample(@Param("record") City record, @Param("example") CityExample example); - - int updateByPrimaryKeySelective(City record); - - int updateByPrimaryKey(City record); -} \ No newline at end of file diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/test/ClientFactory.java b/quick-ElasticSearch/src/main/java/com/quick/es/test/ClientFactory.java deleted file mode 100644 index 9c8a5382..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/test/ClientFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.quick.es.test; - -//public class ClientFactory { -// -// public static Client nodeClient() { -// // 启动一个本地节点,并加入子网内的ES集群 -// Node node = nodeBuilder() -// .clusterName("elasticsearch") // 要加入的集群名为elasticsearch -// .data(true) // 本嵌入式节点可以保存数据 -// .node(); // 构建并启动本节点 -// -// // 获得一个Client对象,该对象可以对子网内的“elasticsearch”集群进行相关操作。 -// return node.client(); -// } -// -// public static Client transportClient() { -// // 配置信息 -// Settings esSetting = settingsBuilder() -// .put("cluster.name", "elasticsearch") -// .build(); -// TransportClient transportClient = new TransportClient(esSetting); -// -// // 添加连接地址 -// TransportAddress address = new InetSocketTransportAddress("127.0.0.1", 9300); -// transportClient.addTransportAddress(address); -// -// return transportClient; -// } -// -// -//} \ No newline at end of file diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/test/MyNodeClient.java b/quick-ElasticSearch/src/main/java/com/quick/es/test/MyNodeClient.java deleted file mode 100644 index ace7d6b3..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/test/MyNodeClient.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.quick.es.test; - -/** - * Created by Administrator on 2017/6/5 0005. - */ -public class MyNodeClient { - public static void main(String[] args) { - // 启动一个本地节点,并加入子网内的ES集群 -// Node node = nodeBuilder() -// .clusterName("elasticsearch") // 要加入的集群名为elasticsearch -// // .client(true) //如果设置为true,则该节点不会保存数据 -// .data(true) // 本嵌入式节点可以保存数据 -// .node(); // 构建并启动本节点 -// -// // 获得一个Client对象,该对象可以对子网内的“elasticsearch”集群进行相关操作。 -// Client nodeClient = node.client(); - } -} diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/test/RepeateColumn.java b/quick-ElasticSearch/src/main/java/com/quick/es/test/RepeateColumn.java deleted file mode 100644 index 4c2853c9..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/test/RepeateColumn.java +++ /dev/null @@ -1,99 +0,0 @@ -//package com.quick.es.test; -// -//import java.util.ArrayList; -// -///** -// * Created with IDEA -// * User: vector -// * Data: 2017/6/26 -// * Time: 17:12 -// * Description: -// */ -//public class RepeateColumn { -// public static void main(String[] args) { -// ArrayList list = new ArrayList(); -// ArrayList newArrayList = new ArrayList(); -// //初始化测试数据 -// Student student = new Student("A", 50); -// list.add(student); -// student = new Student("A", 35); -// list.add(student); -// student = new Student("B", 100); -// list.add(student); -// student = new Student("C", 45); -// list.add(student); -// student = new Student("B", 45); -// list.add(student); -// -// int j; -// Student studentI;//原始列表中的学生 -// Student studentJ;//整理后的列表中的学生 -// for (int i = 0; i < list.size(); i++) { -// studentI = list.get(i); -// for (j = 0; j < newArrayList.size(); j++) { -// studentJ = newArrayList.get(j); -// if (studentI.getName().equals(studentJ.getName())) {//原始列表中的学生已经在新列表中出现过 -// studentJ.setCount(studentJ.getCount() + 1);//出现次数加一 -// studentJ.setTotal(studentJ.getTotal() + studentI.getScore());//总成绩加上这次的成绩 -// break;//跳出J循环 -// } -// } -// if (j == newArrayList.size()) {//原始列表中的这个学生没在整理后的列表中出现过 -// studentI.setCount(1); -// studentI.setTotal(studentI.getScore()); -// newArrayList.add(studentI); -// } -// -// } -// //用于测试结果 -// Student studentK; -// for (int k = 0; k < newArrayList.size(); k++) { -// studentK = newArrayList.get(k); -// System.out.println(studentK.getName() + "出现" + studentK.getCount() -// + "次, 总成绩:" + studentK.getTotal()); -// } -// } -//} -//class Student { -// private String name; -// private int score; -// private int count; -// private int total; -// -// public Student(String name, int score) { -// this.name = name; -// this.score = score; -// } -// -// public String getName() { -// return name; -// } -// -// public void setName(String name) { -// this.name = name; -// } -// -// public int getScore() { -// return score; -// } -// -// public void setScore(int score) { -// this.score = score; -// } -// -// public int getCount() { -// return count; -// } -// -// public void setCount(int count) { -// this.count = count; -// } -// -// public int getTotal() { -// return total; -// } -// -// public void setTotal(int total) { -// this.total = total; -// } -//} \ No newline at end of file diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/test/RepeateColumn2.java b/quick-ElasticSearch/src/main/java/com/quick/es/test/RepeateColumn2.java deleted file mode 100644 index 4f273e38..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/test/RepeateColumn2.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.quick.es.test; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Created with IDEA - * User: vector - * Data: 2017/6/26 - * Time: 17:49 - * Description: - */ -public class RepeateColumn2 { - private static List stus = new ArrayList(); - - public static void init() { - - for (int i = 0; i < 6; i++) { - Student stu = new Student(); - if (3 > i) { - stu.setName("小明"); - } else if (2 < i && 5 > i) { - stu.setName("小华"); - } else { - stu.setName("小小"); - } - stu.setScore(20D); - stus.add(stu); - } - } - - public static void main(String[] args) { - - init(); - - Map> stuDataMap = new HashMap>(); - for (Student stu : stus) { - String name = stu.getName(); - System.out.println("姓名:" + name); - List dataList = stuDataMap.get(stu.getName()); - if (null == dataList) { - dataList = new ArrayList(); - dataList.add(1); - dataList.add(stu.getScore()); - } else { - int count = (Integer) dataList.get(0) + 1; - double score = (Double) dataList.get(1) + stu.getScore(); - dataList.remove(1); - dataList.remove(0); - dataList.add(count); - dataList.add(score); - } - stuDataMap.put(stu.getName(), dataList); - } - -// 遍历结果 - for (Map.Entry> entry : stuDataMap.entrySet()) { - System.out.println(entry.getKey() + " " + entry.getValue().get(0) + " " + entry.getValue().get(1)); - } - } -} -class Student { - - private String name; - - private double score; - - public Student() { - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public double getScore() { - return score; - } - - public void setScore(double score) { - this.score = score; - } - -} diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/util/DataFactory.java b/quick-ElasticSearch/src/main/java/com/quick/es/util/DataFactory.java deleted file mode 100644 index 6af499db..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/util/DataFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.quick.es.util; - -import com.quick.es.entity.City; - -import java.util.ArrayList; -import java.util.List; - -public class DataFactory { - public static DataFactory dataFactory = new DataFactory(); - - private DataFactory() { - } - - public DataFactory getInstance() { - return dataFactory; - } - - public static List getInitJsonData() { - List list = new ArrayList(); - String data1 = JsonUtil.model2Json(new City(6, "Kabul", "AFG", "Herat",1780000)); -// String data2 = JsonUtil.model2Json(new City(2, "Qandahar", "AFG", "Qandahar",237500)); -// String data3 = JsonUtil.model2Json(new City(3, "Herat", "AFG", "Herat",186800)); -// String data4 = JsonUtil.model2Json(new City(4, "Mazar-e-Sharif", "AFG", "Balkh",127800)); -// String data5 = JsonUtil.model2Json(new City(5, "Amsterdam", "AFG", "Noord-Holland",731200)); - list.add(data1); -// list.add(data2); -// list.add(data3); -// list.add(data4); -// list.add(data5); - return list; - } - -} \ No newline at end of file diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/util/JsonUtil.java b/quick-ElasticSearch/src/main/java/com/quick/es/util/JsonUtil.java deleted file mode 100644 index 31b4b06c..00000000 --- a/quick-ElasticSearch/src/main/java/com/quick/es/util/JsonUtil.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.quick.es.util; - -import com.quick.es.entity.City; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; - -import java.io.IOException; - -public class JsonUtil { - - // Java实体对象转json对象 - public static String model2Json(City city) { - String jsonData = null; - try { - XContentBuilder jsonBuild = XContentFactory.jsonBuilder(); - jsonBuild.startObject() - .field("id", city.getId()) - .field("name", city.getName()) - .field("countrycode", city.getCountrycode()) - .field("district", city.getDistrict()) - .field("population", city.getPopulation()) - .endObject(); - - jsonData = jsonBuild.string(); - - } catch (IOException e) { - e.printStackTrace(); - } - return jsonData; - } - -} \ No newline at end of file diff --git a/quick-ElasticSearch/src/main/resources/application.properties b/quick-ElasticSearch/src/main/resources/application.properties deleted file mode 100644 index b7905145..00000000 --- a/quick-ElasticSearch/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -logging.level.root=info diff --git a/quick-ElasticSearch/src/main/resources/generatorConfig.xml b/quick-ElasticSearch/src/main/resources/generatorConfig.xml deleted file mode 100644 index 02b06cb4..00000000 --- a/quick-ElasticSearch/src/main/resources/generatorConfig.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
diff --git a/quick-ElasticSearch/src/main/resources/log4j2.properties b/quick-ElasticSearch/src/main/resources/log4j2.properties deleted file mode 100644 index fced116e..00000000 --- a/quick-ElasticSearch/src/main/resources/log4j2.properties +++ /dev/null @@ -1,6 +0,0 @@ -appender.console.type = Console -appender.console.name = console -appender.console.layout.type = PatternLayout - -rootLogger.level = info -rootLogger.appenderRef.console.ref = console \ No newline at end of file diff --git a/quick-abstract-template/README.md b/quick-abstract-template/README.md new file mode 100644 index 00000000..db8013f2 --- /dev/null +++ b/quick-abstract-template/README.md @@ -0,0 +1 @@ +![img.png](img.png) \ No newline at end of file diff --git a/quick-abstract-template/img.png b/quick-abstract-template/img.png new file mode 100644 index 00000000..8003ebd8 Binary files /dev/null and b/quick-abstract-template/img.png differ diff --git a/quick-abstract-template/pom.xml b/quick-abstract-template/pom.xml new file mode 100644 index 00000000..0bc7cad7 --- /dev/null +++ b/quick-abstract-template/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + com.quick.abs + quick-abstract-template + + + 8 + 8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.projectlombok + lombok + + + + \ No newline at end of file diff --git a/quick-abstract-template/src/main/java/com/quick/abs/AbstractTemplateApplication.java b/quick-abstract-template/src/main/java/com/quick/abs/AbstractTemplateApplication.java new file mode 100644 index 00000000..f5a415f6 --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/AbstractTemplateApplication.java @@ -0,0 +1,18 @@ +package com.quick.abs; + +import com.quick.abs.service.CreatureLifeService; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +@SpringBootApplication +public class AbstractTemplateApplication { + + + public static void main(String[] args) { + ConfigurableApplicationContext applicationContext = SpringApplication.run(AbstractTemplateApplication.class); + + CreatureLifeService creatureLifeService = applicationContext.getBean("creatureLifeService",CreatureLifeService.class); + creatureLifeService.birth2die("Duck"); + } +} \ No newline at end of file diff --git a/quick-abstract-template/src/main/java/com/quick/abs/factory/AbstractBirdFactory.java b/quick-abstract-template/src/main/java/com/quick/abs/factory/AbstractBirdFactory.java new file mode 100644 index 00000000..5cacf47d --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/factory/AbstractBirdFactory.java @@ -0,0 +1,30 @@ +package com.quick.abs.factory; + +import com.quick.abs.strategy.base.AbstractBird; +import com.quick.abs.strategy.base.Animal; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * 生物工场 + */ +@Slf4j +public class AbstractBirdFactory { + private static Map map = new HashMap<>(); + + public static void register(String key, AbstractBird bird) { + if (StringUtils.isEmpty(key) && null == bird) { + return; + } + map.put(key, bird); + } + + public static AbstractBird getInvokeHandler(String key) { + log.info("register {}", key); + return map.get(key); + } + +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/service/CreatureLifeService.java b/quick-abstract-template/src/main/java/com/quick/abs/service/CreatureLifeService.java new file mode 100644 index 00000000..9e1cbcf9 --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/service/CreatureLifeService.java @@ -0,0 +1,37 @@ +package com.quick.abs.service; + +import com.quick.abs.factory.AbstractBirdFactory; +import com.quick.abs.strategy.base.AbstractBird; +import com.quick.abs.strategy.base.component.Oxygen; +import com.quick.abs.strategy.base.component.Water; +import org.springframework.stereotype.Service; + +@Service +public class CreatureLifeService { + + + public void birth2die(String key) { + AbstractBird bird = AbstractBirdFactory.getInvokeHandler(key); + + // 活着 + bird.isAlive(); + + // 新晨代谢 + bird.metabolism(new Oxygen(), new Water()); + + System.out.println(bird.getFeather()); + + + // 飞翔 + bird.getLeftWing().action(); + bird.getLeftWing().action(); + + // 可以繁殖 + bird.reproduction(); + + bird.layEggs(); + + + } + +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/DonaldDuck.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/DonaldDuck.java new file mode 100644 index 00000000..ffb14f8d --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/DonaldDuck.java @@ -0,0 +1,23 @@ +package com.quick.abs.strategy; + +import com.quick.abs.factory.AbstractBirdFactory; +import com.quick.abs.strategy.base.ability.Fly; +import com.quick.abs.strategy.base.ability.Speak; +import org.springframework.stereotype.Component; + +@Component +public class DonaldDuck extends Duck implements Speak, Fly { + + + + @Override + public void afterPropertiesSet() throws Exception { + AbstractBirdFactory.register("DonaldDuck", this); + } + + + @Override + public void action() { + System.out.println(this.getClass().getName()+" can speak"); + } +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/Duck.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/Duck.java new file mode 100644 index 00000000..e1e51edf --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/Duck.java @@ -0,0 +1,13 @@ +package com.quick.abs.strategy; + +import com.quick.abs.factory.AbstractBirdFactory; +import com.quick.abs.strategy.base.AbstractBird; +import org.springframework.stereotype.Component; + +@Component +public class Duck extends AbstractBird { + @Override + public void afterPropertiesSet() throws Exception { + AbstractBirdFactory.register("Duck", this); + } +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/Penguin.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/Penguin.java new file mode 100644 index 00000000..720c7ca6 --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/Penguin.java @@ -0,0 +1,18 @@ +package com.quick.abs.strategy; + +import com.quick.abs.factory.AbstractBirdFactory; +import com.quick.abs.strategy.base.AbstractBird; +import org.springframework.stereotype.Component; + +/** + * 企鹅 + */ +@Component +public class Penguin extends AbstractBird { + + + @Override + public void afterPropertiesSet() throws Exception { + AbstractBirdFactory.register("Penguin", this); + } +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/WildGoose.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/WildGoose.java new file mode 100644 index 00000000..aafd4705 --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/WildGoose.java @@ -0,0 +1,36 @@ +package com.quick.abs.strategy; + +import com.quick.abs.factory.AbstractBirdFactory; +import com.quick.abs.strategy.base.AbstractBird; +import com.quick.abs.strategy.base.ability.Fly; +import org.springframework.stereotype.Component; + +/** + * 大雁 + */ +@Component +public class WildGoose extends AbstractBird implements Fly { + + + @Override + public void afterPropertiesSet() throws Exception { + AbstractBirdFactory.register("WildGoose", this); + } + + + public void flyByV(){ + action(); + System.out.println("use V fly"); + } + + public void flyByLine(){ + action(); + System.out.println("use Line fly"); + } + + + @Override + public void action() { + System.out.println(this.getClass().getName()+" can fly"); + } +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/AbstractBird.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/AbstractBird.java new file mode 100644 index 00000000..ae81b611 --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/AbstractBird.java @@ -0,0 +1,46 @@ +package com.quick.abs.strategy.base; + +import com.quick.abs.strategy.base.component.Oxygen; +import com.quick.abs.strategy.base.component.Water; +import com.quick.abs.strategy.base.component.Wing; +import lombok.Data; + +import java.util.List; + +@Data +public abstract class AbstractBird extends Animal { + + private Wing leftWing; + private Wing rightWing; + + /** + * 羽毛 + */ + private String feather; + + public AbstractBird() { + this.leftWing = new Wing(); + this.rightWing = new Wing(); + this.feather = "红色的羽毛"; + } + + /** + * 下蛋 + * + * @return + */ + public int layEggs() { + System.out.println(this.getClass().getName() + " layEggs"); + return 1; + } + + @Override + public void metabolism(Oxygen oxygen, Water water) { + System.out.println(this.getClass().getName() + " 需要氧气和水"); + } + + @Override + public void reproduction() { + System.out.println(this.getClass().getName() + " 可以繁殖"); + } +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/Animal.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/Animal.java new file mode 100644 index 00000000..8298a0fe --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/Animal.java @@ -0,0 +1,33 @@ +package com.quick.abs.strategy.base; + +import com.quick.abs.strategy.base.component.Oxygen; +import com.quick.abs.strategy.base.component.Water; + +import java.util.List; + +public abstract class Animal implements Creature { + + /** + * 是否活着 + * + * @return 默认活着 + */ + public boolean isAlive() { + return true; + } + + + /** + * 新陈代谢 + */ + public abstract void metabolism(Oxygen oxygen, Water water); + + /** + * 繁殖 + * + * @return 小动物 + */ + public abstract void reproduction(); + + +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/Creature.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/Creature.java new file mode 100644 index 00000000..2b812207 --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/Creature.java @@ -0,0 +1,17 @@ +package com.quick.abs.strategy.base; + +import org.springframework.beans.factory.InitializingBean; + +/** + * 生物 + */ +public interface Creature extends InitializingBean { + + /** + * 生物 + * @return + */ + default boolean iThinkIam(){ + return true; + } +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/ability/BaseAbility.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/ability/BaseAbility.java new file mode 100644 index 00000000..c0c05c7f --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/ability/BaseAbility.java @@ -0,0 +1,5 @@ +package com.quick.abs.strategy.base.ability; + +public interface BaseAbility { + void action(); +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/ability/Fly.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/ability/Fly.java new file mode 100644 index 00000000..34830ada --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/ability/Fly.java @@ -0,0 +1,6 @@ +package com.quick.abs.strategy.base.ability; + +public interface Fly extends BaseAbility { + @Override + void action(); +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/ability/Speak.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/ability/Speak.java new file mode 100644 index 00000000..4e9c7c4a --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/ability/Speak.java @@ -0,0 +1,6 @@ +package com.quick.abs.strategy.base.ability; + +public interface Speak extends BaseAbility { + @Override + void action(); +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/component/Oxygen.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/component/Oxygen.java new file mode 100644 index 00000000..c18db10e --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/component/Oxygen.java @@ -0,0 +1,4 @@ +package com.quick.abs.strategy.base.component; + +public class Oxygen { +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/component/Water.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/component/Water.java new file mode 100644 index 00000000..14595c81 --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/component/Water.java @@ -0,0 +1,4 @@ +package com.quick.abs.strategy.base.component; + +public class Water { +} diff --git a/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/component/Wing.java b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/component/Wing.java new file mode 100644 index 00000000..feaabd59 --- /dev/null +++ b/quick-abstract-template/src/main/java/com/quick/abs/strategy/base/component/Wing.java @@ -0,0 +1,10 @@ +package com.quick.abs.strategy.base.component; + +/** + * 翅膀 + */ +public class Wing { + public void action() { + System.out.println("挥动翅膀"); + } +} diff --git a/quick-abstract-template/src/main/resources/application.yml b/quick-abstract-template/src/main/resources/application.yml new file mode 100644 index 00000000..47fbb02d --- /dev/null +++ b/quick-abstract-template/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 8080 \ No newline at end of file diff --git a/quick-abstract-template2/pom.xml b/quick-abstract-template2/pom.xml new file mode 100644 index 00000000..6c234c53 --- /dev/null +++ b/quick-abstract-template2/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + com.quick.tp2 + quick-abstract-template2 + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/quick-abstract-template2/src/main/java/com/quick/tp2/Main.java b/quick-abstract-template2/src/main/java/com/quick/tp2/Main.java new file mode 100644 index 00000000..21f4ac9a --- /dev/null +++ b/quick-abstract-template2/src/main/java/com/quick/tp2/Main.java @@ -0,0 +1,7 @@ +package com.quick.tp2; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello world!"); + } +} \ No newline at end of file diff --git a/quick-activemq/README.md b/quick-activemq/README.md new file mode 100644 index 00000000..7aaeb808 --- /dev/null +++ b/quick-activemq/README.md @@ -0,0 +1,23 @@ +```bash +docker pull webcenter/activemq + + +docker run --name='activemq' \ + -itd \ + -p 8161:8161 \ + -p 61616:61616 \ + -e ACTIVEMQ_ADMIN_LOGIN=admin \ + -e ACTIVEMQ_ADMIN_PASSWORD=123456 \ + --restart=always \ + -v D:\\develop\\docker-data\\activemq:/data/activemq \ + -v D:\\develop\\docker-data\\activemq\\log:/var/log/activemq \ + webcenter/activemq +``` + + +- 61616是 activemq 的容器使用端口 +- 8161是 web 页面管理端口 +- /usr/soft/activemq 是将activeMQ运行文件挂载到该目录 +- /usr/soft/activemq/log是将activeMQ运行日志挂载到该目录 +- -e ACTIVEMQ_ADMIN_LOGIN=admin 指定登录名 +- -e ACTIVEMQ_ADMIN_PASSWORD=123456 登录密码 \ No newline at end of file diff --git a/quick-activemq/pom.xml b/quick-activemq/pom.xml new file mode 100644 index 00000000..7745c0eb --- /dev/null +++ b/quick-activemq/pom.xml @@ -0,0 +1,57 @@ + + + quick-activemq + 1.0-SNAPSHOT + 4.0.0 + + https://activemq.apache.org/artemis/docs/1.0.0/duplicate-detection.html + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-activemq + + + org.springframework.boot + spring-boot-starter-artemis + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.alibaba + fastjson + + + + commons-io + commons-io + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/quick-activemq/src/main/java/com/mq/ActiveMqApplication.java b/quick-activemq/src/main/java/com/mq/ActiveMqApplication.java new file mode 100644 index 00000000..d27fe726 --- /dev/null +++ b/quick-activemq/src/main/java/com/mq/ActiveMqApplication.java @@ -0,0 +1,18 @@ +package com.mq; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Created with IDEA + * User: vector + * Data: 2017/12/20 + * Time: 17:31 + * Description:http://javasampleapproach.com/spring-framework/spring-jms/activemq-create-springboot-activemq-response-management-application-sendto-annotation + */ +@SpringBootApplication +public class ActiveMqApplication { + public static void main(String[] args) { + SpringApplication.run(ActiveMqApplication.class); + } +} diff --git a/quick-activemq/src/main/java/com/mq/client/JMSConsumerThread.java b/quick-activemq/src/main/java/com/mq/client/JMSConsumerThread.java new file mode 100644 index 00000000..1c29216a --- /dev/null +++ b/quick-activemq/src/main/java/com/mq/client/JMSConsumerThread.java @@ -0,0 +1,42 @@ +package com.mq.client; + +import org.apache.activemq.ActiveMQConnection; +import org.apache.activemq.ActiveMQConnectionFactory; + +import javax.jms.*; + +public class JMSConsumerThread { + + private static final String USERNAME= ActiveMQConnection.DEFAULT_USER; // 默认的连接用户名 + private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD; // 默认的连接密码 + private static final String BROKEURL=ActiveMQConnection.DEFAULT_BROKER_URL; // 默认的连接地址 + + ConnectionFactory connectionFactory=null; // 连接工厂 + private Connection connection = null; + private Session session = null; + private Destination destination=null; // 消息的目的地 + public void init(){ + // 实例化连接工厂 + connectionFactory=new ActiveMQConnectionFactory(JMSConsumerThread.USERNAME, JMSConsumerThread.PASSWORD, JMSConsumerThread.BROKEURL); + try { + connection=connectionFactory.createConnection(); // 通过连接工厂获取连接 + connection.start(); + } catch (JMSException e) { + e.printStackTrace(); + } + } + public void consumer(){ + MessageConsumer messageConsumer; // 消息的消费者 + + try { + session=connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); // 创建Session + destination=session.createQueue("queue1"); + messageConsumer=session.createConsumer(destination); // 创建消息消费者 + messageConsumer.setMessageListener(new Listener3()); // 注册消息监听 + } catch (JMSException e) { + e.printStackTrace(); + } + } + + +} \ No newline at end of file diff --git a/quick-activemq/src/main/java/com/mq/client/JMSProducerThread.java b/quick-activemq/src/main/java/com/mq/client/JMSProducerThread.java new file mode 100644 index 00000000..97c08942 --- /dev/null +++ b/quick-activemq/src/main/java/com/mq/client/JMSProducerThread.java @@ -0,0 +1,60 @@ +package com.mq.client; + +import org.apache.activemq.ActiveMQConnection; +import org.apache.activemq.ActiveMQConnectionFactory; + +import javax.jms.*; + +public class JMSProducerThread { + + private static final String USERNAME= ActiveMQConnection.DEFAULT_USER; // 默认的连接用户名 + private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD; // 默认的连接密码 + private static final String BROKEURL=ActiveMQConnection.DEFAULT_BROKER_URL; // 默认的连接地址 + private static final int SENDNUM=10; // 发送的消息数量 + ConnectionFactory connectionFactory=null; // 连接工厂 + private Connection connection = null; + private Session session = null; + private Destination destination=null; // 消息的目的地 + public void init(){ + // 实例化连接工厂 + connectionFactory=new ActiveMQConnectionFactory(JMSProducerThread.USERNAME, JMSProducerThread.PASSWORD, JMSProducerThread.BROKEURL); + try { + connection=connectionFactory.createConnection(); // 通过连接工厂获取连接 + connection.start(); + } catch (JMSException e) { + e.printStackTrace(); + } + } + public void produce(){ + try { + MessageProducer messageProducer; // 消息生产者 + session=connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); // 创建Session + destination=session.createQueue("queue1"); + messageProducer=session.createProducer(destination); // 创建消息生产者 + for(int i=0;i { +// msg.setStringProperty(HDR_DUPLICATE_DETECTION_ID.toString(),"123"); +// return msg; +// }); + jmsTemplate.convertAndSend(destination, message); + } +} \ No newline at end of file diff --git a/quick-activemq/src/main/java/com/mq/utils/ExtractMsgUtil.java b/quick-activemq/src/main/java/com/mq/utils/ExtractMsgUtil.java new file mode 100644 index 00000000..8c205e56 --- /dev/null +++ b/quick-activemq/src/main/java/com/mq/utils/ExtractMsgUtil.java @@ -0,0 +1,61 @@ +package com.mq.utils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.artemis.jms.client.ActiveMQObjectMessage; +import org.apache.activemq.command.ActiveMQTextMessage; +import org.apache.activemq.util.ByteSequence; +import org.apache.commons.io.IOUtils; + +import javax.jms.*; +import java.io.*; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +/** + * @author vector + * @date: 2019/1/21 0021 10:11 + */ +public class ExtractMsgUtil { + + public static void main(String[] args) { + try { + getMQMsg(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + + public static void getMQMsg() throws JMSException { + ConnectionFactory connectionFactory = + new ActiveMQConnectionFactory("tcp://x.x.x.x:61616"); + Connection connection = + connectionFactory.createConnection("admin","admin"); + connection.start(); + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Queue queue = session.createQueue("queuename"); + QueueBrowser queueBrowser = session.createBrowser(queue); + Enumeration msgs = queueBrowser.getEnumeration(); + List temp = new ArrayList<>(); + while (msgs.hasMoreElements()) { + try { + ActiveMQTextMessage msg = (ActiveMQTextMessage)msgs.nextElement(); + JSONObject jsonObject = JSON.parseObject(msg.getText()); + temp.add(jsonObject.getString("ResumeId")); + } catch (Exception e) { + e.printStackTrace(); + } + } + System.out.println("save to file"); + try { + IOUtils.writeLines(temp,"\n",new FileOutputStream(new File("D:\\activemq-resume-msg.txt")), Charset.defaultCharset()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/quick-activemq/src/main/resources/application.properties b/quick-activemq/src/main/resources/application.properties new file mode 100644 index 00000000..7f15985e --- /dev/null +++ b/quick-activemq/src/main/resources/application.properties @@ -0,0 +1,9 @@ +server.port=8080 + +spring.activemq.broker-url=tcp://localhost:61616 +spring.activemq.user=admin +spring.activemq.password=123456 +spring.activemq.in-memory=true +spring.activemq.pool.enabled=false + +jsa.activemq.queue.name=test_queue diff --git a/quick-activemq2/pom.xml b/quick-activemq2/pom.xml new file mode 100644 index 00000000..7f721c8a --- /dev/null +++ b/quick-activemq2/pom.xml @@ -0,0 +1,55 @@ + + + quick-activemq2 + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-activemq + + + org.springframework.boot + spring-boot-starter-artemis + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.alibaba + fastjson + + + org.projectlombok + lombok + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/quick-activemq2/src/main/java/com/active2/Activemq2Application.java b/quick-activemq2/src/main/java/com/active2/Activemq2Application.java new file mode 100644 index 00000000..8a16a874 --- /dev/null +++ b/quick-activemq2/src/main/java/com/active2/Activemq2Application.java @@ -0,0 +1,19 @@ +package com.active2; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @Author: wangxc + * @GitHub: https://github.com/vector4wang + * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents + * @BLOG: http://vector4wang.tk + * @wxid: BMHJQS + */ +@SpringBootApplication +public class Activemq2Application { + + public static void main(String[] args) { + SpringApplication.run(Activemq2Application.class, args); + } +} diff --git a/quick-activemq2/src/main/java/com/active2/config/AcitveMQConfig.java b/quick-activemq2/src/main/java/com/active2/config/AcitveMQConfig.java new file mode 100644 index 00000000..6bd4d347 --- /dev/null +++ b/quick-activemq2/src/main/java/com/active2/config/AcitveMQConfig.java @@ -0,0 +1,170 @@ +package com.active2.config; + +import com.active2.mq.TestQueueConsumer; +import lombok.extern.slf4j.Slf4j; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.RedeliveryPolicy; +import org.apache.activemq.artemis.jms.client.ActiveMQQueue; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.jms.annotation.EnableJms; +import org.springframework.jms.config.DefaultJmsListenerContainerFactory; +import org.springframework.jms.core.JmsTemplate; + +import javax.jms.Queue; + +/** + * Created with IDEA + * User: vector + * Data: 2018/4/26 0026 + * Time: 10:28 + * Description: + * AUTO_ACKNOWLEDGE = 1 自动确认 + * CLIENT_ACKNOWLEDGE = 2 客户端手动确认 + * DUPS_OK_ACKNOWLEDGE = 3 自动批量确认 + * SESSION_TRANSACTED = 0 事务提交并确认 + * INDIVIDUAL_ACKNOWLEDGE = 4 单条消息确认 activemq 独有 + * + * + * 默认从上到下创建Bean + */ +@ConditionalOnProperty(value = "activemq.switch") +@EnableJms +@Configuration +@Slf4j +public class AcitveMQConfig { + + + @Value("${jsa.activemq.queue.names.concurrency}") + private String queuesConcurrency; + + @Value("${jsa.activemq.simple.names.concurrency}") + private String simpeQueuesConcurrency; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private AbstractEnvironment environment; + + @Bean + public Queue queue() { + return new ActiveMQQueue("queue1"); + } + + + + @Bean + public RedeliveryPolicy redeliveryPolicy() { + log.info("init redeliveryPolicy"); + RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy(); + //是否在每次尝试重新发送失败后,增长这个等待时间 + redeliveryPolicy.setUseExponentialBackOff(true); + //重发次数,默认为6次 这里设置为10次 + redeliveryPolicy.setMaximumRedeliveries(10); + //重发时间间隔,默认为1秒 + redeliveryPolicy.setInitialRedeliveryDelay(1); + //第一次失败后重新发送之前等待500毫秒,第二次失败再等待500 * 2毫秒,这里的2就是value + redeliveryPolicy.setBackOffMultiplier(2); + //是否避免消息碰撞 + redeliveryPolicy.setUseCollisionAvoidance(false); + //设置重发最大拖延时间-1 表示没有拖延只有UseExponentialBackOff(true)为true时生效 + redeliveryPolicy.setMaximumRedeliveryDelay(-1); + + return redeliveryPolicy; + } + + @Bean + public ActiveMQConnectionFactory activeMQConnectionFactory(@Value("${activemq.url}") String url, RedeliveryPolicy redeliveryPolicy) { + log.info("init activeMQConnectionFactory"); + ActiveMQConnectionFactory activeMQConnectionFactory = + new ActiveMQConnectionFactory( + "admin", + "admin", + url); + activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy); + return activeMQConnectionFactory; + } + + @Bean + public JmsTemplate jmsTemplate(ActiveMQConnectionFactory activeMQConnectionFactory, Queue queue) { + log.info("init jmsTemplate"); + JmsTemplate jmsTemplate = new JmsTemplate(); + jmsTemplate.setDeliveryMode(2);//进行持久化配置 1表示非持久化,2表示持久化 + jmsTemplate.setConnectionFactory(activeMQConnectionFactory); + jmsTemplate.setDefaultDestination(queue); //此处可不设置默认,在发送消息时也可设置队列 + jmsTemplate.setSessionAcknowledgeMode(4);//客户端签收模式 + return jmsTemplate; + } + + /** + * 有时间会使用下面方式创建多个类型相同不同beanname的bean,这里要注意相关的启动顺序 + */ + @Bean + public Runnable dynamicConfiguration() throws Exception { + log.info("init jmsQueueListeners"); + String[] concurrencys = queuesConcurrency.split(","); + ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext; + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); + for (int i = 1; i <= 5; i++) { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultJmsListenerContainerFactory.class); + /** + * 设置属性 + */ + beanDefinitionBuilder.addPropertyValue("connectionFactory", applicationContext.getBean("activeMQConnectionFactory")); + beanDefinitionBuilder.addPropertyValue("concurrency", concurrencys[i-1]); + beanDefinitionBuilder.addPropertyValue("recoveryInterval", 1000L); + beanDefinitionBuilder.addPropertyValue("sessionAcknowledgeMode", 2); + + /** + * 注册到spring容器中 + */ + beanFactory.registerBeanDefinition("jmsQueueListener" + i, beanDefinitionBuilder.getBeanDefinition()); + } + return null; + } + + @Bean + public TestQueueConsumer consumer() { + + log.info("init jmsQueueListener Consumer"); + return new TestQueueConsumer(); + } + + + + @Bean + public Runnable dynamicConfiguration4Simpe() throws Exception { + log.info("init jmsSimpeQueueListeners"); + String[] concurrencys = simpeQueuesConcurrency.split(","); + ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext; + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); + for (int i = 1; i <= 5; i++) { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultJmsListenerContainerFactory.class); + /** + * 设置属性 + */ + beanDefinitionBuilder.addPropertyValue("connectionFactory", applicationContext.getBean("activeMQConnectionFactory")); + beanDefinitionBuilder.addPropertyValue("concurrency", concurrencys[i-1]); + beanDefinitionBuilder.addPropertyValue("recoveryInterval", 1000L); + beanDefinitionBuilder.addPropertyValue("sessionAcknowledgeMode", 2); + + /** + * 注册到spring容器中 + */ + beanFactory.registerBeanDefinition("jmsSimpeQueueListener" + i, beanDefinitionBuilder.getBeanDefinition()); + } + return null; + } + + +} + diff --git a/quick-activemq2/src/main/java/com/active2/config/DynamicPropertySource.java b/quick-activemq2/src/main/java/com/active2/config/DynamicPropertySource.java new file mode 100644 index 00000000..fd5dca15 --- /dev/null +++ b/quick-activemq2/src/main/java/com/active2/config/DynamicPropertySource.java @@ -0,0 +1,52 @@ +package com.active2.config; + +import org.springframework.core.env.MapPropertySource; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author wangxc + * @date: 2019/3/6 下午10:11 + * + */ +public class DynamicPropertySource extends MapPropertySource { + + + public DynamicPropertySource(String name, Map source) { + super(name, source); + } + + public static Builder builder(){ + return new Builder(); + } + + public static class Builder{ + private String sourceName; + private Map sourceMap = new HashMap<>(); + + public Builder setSourceName(String sourceName) { + this.sourceName = sourceName; + return this; + } + + public Builder setProperty(String key, String value) { + this.sourceMap.put(key, value); + return this; + } + + public Builder setProperty(Map sourceMap) { + this.sourceMap.putAll(sourceMap); + return this; + + } + + public DynamicPropertySource build() { + return new DynamicPropertySource(this.sourceName,this.sourceMap); + } + + } + +} diff --git a/quick-activemq2/src/main/java/com/active2/mq/SimpeQueueConsumer.java b/quick-activemq2/src/main/java/com/active2/mq/SimpeQueueConsumer.java new file mode 100644 index 00000000..3c6b96a5 --- /dev/null +++ b/quick-activemq2/src/main/java/com/active2/mq/SimpeQueueConsumer.java @@ -0,0 +1,59 @@ +package com.active2.mq; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.jms.annotation.JmsListener; + +import javax.jms.JMSException; +import javax.jms.Session; +import javax.jms.TextMessage; + +/** + * @Author: wangxc + * @GitHub: https://github.com/vector4wang + * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents + * @BLOG: http://vector4wang.tk + * @wxid: BMHJQS + */ +@Slf4j +public class SimpeQueueConsumer { + + + @JmsListener(destination = "${jsa.activemq.simple.queue.name_1}", containerFactory = "jmsSimpeQueueListener1") + public void receiveQueue1(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer1收到的报文为:" + text.getText()); + } + + @JmsListener(destination = "${jsa.activemq.simple.queue.name_2}", containerFactory = "jmsSimpeQueueListener2") + public void receiveQueue2(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer2收到的报文为:" + text.getText()); + } + + @JmsListener(destination = "${jsa.activemq.simple.queue.name_3}", containerFactory = "jmsSimpeQueueListener3") + public void receiveQueue3(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer3收到的报文为:" + text.getText()); + } + + @JmsListener(destination = "${jsa.activemq.simple.queue.name_4}", containerFactory = "jmsSimpeQueueListener4") + public void receiveQueue4(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer4收到的报文为:" + text.getText()); + } + + @JmsListener(destination = "${jsa.activemq.simple.queue.name_5}", containerFactory = "jmsSimpeQueueListener5") + public void receiveQueue5(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer5收到的报文为:" + text.getText()); + } + + private void doMsg(TextMessage text, Session session, String message) throws JMSException { + try { + log.info(text.getText()); + text.acknowledge();// 使用手动签收模式,需要手动的调用,如果不在catch中调用session.recover()消息只会在重启服务后重发 + } catch (Exception e) { + session.recover();// 此不可省略 重发信息使用 + } + } +} diff --git a/quick-activemq2/src/main/java/com/active2/mq/SingleConsumer.java b/quick-activemq2/src/main/java/com/active2/mq/SingleConsumer.java new file mode 100644 index 00000000..d76cba5e --- /dev/null +++ b/quick-activemq2/src/main/java/com/active2/mq/SingleConsumer.java @@ -0,0 +1,25 @@ +package com.active2.mq; + +import org.springframework.jms.annotation.JmsListener; + +import javax.jms.JMSException; +import javax.jms.Session; +import javax.jms.TextMessage; + +/** + * + * @author wangxc + * @date: 2019/3/6 下午11:21 + * + */ +public class SingleConsumer { + @JmsListener(destination = "${jsa.activemq.simple.queue.name_1}", containerFactory = "jmsSimpeQueueListener1") + public void receiveQueue1(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer1收到的报文为:" + text.getText()); + } + + private void doMsg(TextMessage text, Session session, String s) { + System.out.println(text); + } +} diff --git a/quick-activemq2/src/main/java/com/active2/mq/TestQueueConsumer.java b/quick-activemq2/src/main/java/com/active2/mq/TestQueueConsumer.java new file mode 100644 index 00000000..497a6015 --- /dev/null +++ b/quick-activemq2/src/main/java/com/active2/mq/TestQueueConsumer.java @@ -0,0 +1,61 @@ +package com.active2.mq; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.jms.annotation.JmsListener; + +import javax.jms.JMSException; +import javax.jms.Session; +import javax.jms.TextMessage; + +/** + * @Author: wangxc + * @GitHub: https://github.com/vector4wang + * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents + * @BLOG: http://vector4wang.tk + * @wxid: BMHJQS + */ +@Slf4j +public class TestQueueConsumer { + + + @JmsListener(destination = "${jsa.activemq.queue.name_1}", containerFactory = "jmsQueueListener1") + public void receiveQueue1(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer1收到的报文为:" + text.getText()); + } + + @JmsListener(destination = "${jsa.activemq.queue.name_2}", containerFactory = "jmsQueueListener2") + public void receiveQueue2(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer2收到的报文为:" + text.getText()); + } + + @JmsListener(destination = "${jsa.activemq.queue.name_3}", containerFactory = "jmsQueueListener3") + public void receiveQueue3(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer3收到的报文为:" + text.getText()); + } + + @JmsListener(destination = "${jsa.activemq.queue.name_4}", containerFactory = "jmsQueueListener4") + public void receiveQueue4(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer4收到的报文为:" + text.getText()); + } + + @JmsListener(destination = "${jsa.activemq.queue.name_5}", containerFactory = "jmsQueueListener5") + public void receiveQueue5(final TextMessage text, Session session) + throws JMSException { + doMsg(text, session, "Consumer5收到的报文为:" + text.getText()); + } + + private void doMsg(TextMessage text, Session session, String message) throws JMSException { + try { + log.info(text.getText()); + text.acknowledge();// 使用手动签收模式,需要手动的调用,如果不在catch中调用session.recover()消息只会在重启服务后重发 + } catch (Exception e) { + session.recover();// 此不可省略 重发信息使用 + } + } + + +} diff --git a/quick-activemq2/src/main/resources/application.properties b/quick-activemq2/src/main/resources/application.properties new file mode 100644 index 00000000..82883d9c --- /dev/null +++ b/quick-activemq2/src/main/resources/application.properties @@ -0,0 +1,32 @@ +server.port=8080 + +#spring.activemq.broker-url=tcp://60.205.191.82:8081 +#spring.activemq.user=admin +#spring.activemq.password=admin +#spring.activemq.in-memory=true +#spring.activemq.pool.enabled=false + +activemq.switch=true + +activemq.url=failover:(tcp://localhost:61616) + +jsa.activemq.queue.name_1=test_queue_1 +jsa.activemq.queue.name_2=test_queue_2 +jsa.activemq.queue.name_3=test_queue_3 +jsa.activemq.queue.name_4=test_queue_4 +jsa.activemq.queue.name_5=test_queue_5 + +jsa.activemq.queue.names.concurrency=1,2,3,4,5 + + +jsa.activemq.simple.queue.name_1=simpe_queue_1 +jsa.activemq.simple.queue.name_2=simpe_queue_2 +jsa.activemq.simple.queue.name_3=simpe_queue_3 +jsa.activemq.simple.queue.name_4=simpe_queue_4 +jsa.activemq.simple.queue.name_5=simpe_queue_5 + +jsa.activemq.simple.names.concurrency=1,2,3,4,5 + +test=today + + diff --git a/quick-api-invoker/pom.xml b/quick-api-invoker/pom.xml new file mode 100644 index 00000000..41a712c9 --- /dev/null +++ b/quick-api-invoker/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + com.quick.api.invoker + quick-api-invoker + + + 8 + 8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + com.squareup.okhttp3 + okhttp + + + + \ No newline at end of file diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/Adapter.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/Adapter.java new file mode 100644 index 00000000..e43fc56e --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/Adapter.java @@ -0,0 +1,14 @@ +package com.quick.api.invoker; + +import org.springframework.beans.factory.InitializingBean; + +public interface Adapter extends InitializingBean { + default String getType() { + return this.getClass().getSimpleName(); + } + + @Override + default void afterPropertiesSet() throws Exception { + AdapterContextFactor.register(getType(), this); + } +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/AdapterContextFactor.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/AdapterContextFactor.java new file mode 100644 index 00000000..bf495e8d --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/AdapterContextFactor.java @@ -0,0 +1,31 @@ +package com.quick.api.invoker; + +import com.quick.api.invoker.model.AdapterClassModel; +import lombok.extern.slf4j.Slf4j; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class AdapterContextFactor { + private static Map adapterMap = new HashMap<>(); + + public static void register(String className, Adapter adapter) { + log.info(" ===>>>> register adapter: {}", className); + adapterMap.put(className, adapter); + } + + + public static T getAdapter(String name, Class clazz) { + return (T) adapterMap.get(name); + } + + public static T getAdapter(AdapterClassModel adapterClassModel) throws ClassNotFoundException { + if (adapterClassModel.getLoadType() == 1) { + return (T) getAdapter(adapterClassModel.getClassName(), Class.forName(adapterClassModel.getClassFqn())); + } else { + // TODO 实时加载代码源码 + return null; + } + } +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/Main.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/Main.java new file mode 100644 index 00000000..f06420fa --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/Main.java @@ -0,0 +1,12 @@ +package com.quick.api.invoker; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class); + } +} \ No newline at end of file diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/enums/HttpMethod.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/enums/HttpMethod.java new file mode 100644 index 00000000..ee926a3b --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/enums/HttpMethod.java @@ -0,0 +1,41 @@ +package com.quick.api.invoker.enums; + + +import java.util.HashMap; +import java.util.Map; + + +public enum HttpMethod { + GET, + HEAD, + POST, + PUT, + PATCH, + DELETE, + OPTIONS, + TRACE; + + private static final Map mappings = new HashMap(16); + + private HttpMethod() { + } + + public static HttpMethod resolve( String method) { + return method != null ? (HttpMethod)mappings.get(method) : null; + } + + public boolean matches(String method) { + return this == resolve(method); + } + + static { + HttpMethod[] var0 = values(); + int var1 = var0.length; + + for(int var2 = 0; var2 < var1; ++var2) { + HttpMethod httpMethod = var0[var2]; + mappings.put(httpMethod.name(), httpMethod); + } + + } +} \ No newline at end of file diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/AbstractExecutorAdapter.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/AbstractExecutorAdapter.java new file mode 100644 index 00000000..4a75e3b2 --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/AbstractExecutorAdapter.java @@ -0,0 +1,16 @@ +package com.quick.api.invoker.executor; + +import com.quick.api.invoker.Adapter; +import com.quick.api.invoker.enums.HttpMethod; +import com.quick.api.invoker.executor.bo.ExecutorConfig; + +import java.util.Map; + +public abstract class AbstractExecutorAdapter implements Adapter { + + public abstract String execute(String url, HttpMethod method, Map params, ExecutorConfig config); + + public void print() { + System.out.println(this.getClass().getName()); + } +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/HttpExecutorAdapter.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/HttpExecutorAdapter.java new file mode 100644 index 00000000..a27c812b --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/HttpExecutorAdapter.java @@ -0,0 +1,54 @@ +package com.quick.api.invoker.executor; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.Method; +import cn.hutool.json.JSONUtil; +import com.quick.api.invoker.enums.HttpMethod; +import com.quick.api.invoker.executor.bo.ExecutorConfig; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Objects; + +@Component +public class HttpExecutorAdapter extends AbstractExecutorAdapter { + + + private void addConfig(HttpRequest request, ExecutorConfig config) { + if (Objects.isNull(config)) { + return; + } + if (MapUtil.isNotEmpty(config.getHeader())) { + request.headerMap(config.getHeader(), true); + } + + if (Objects.nonNull(config.getProxy())) { + request.setProxy(config.getProxy()); + } + request.timeout(config.getTimeout()); + + } + + + @Override + public String execute(String url, HttpMethod method, Map params, ExecutorConfig config) { + HttpRequest request = HttpUtil.createRequest(Method.valueOf(method.name()), url); + + if (MapUtil.isNotEmpty(params)) { + if (HttpMethod.GET.equals(method)) { + request.form(params); + } else if (HttpMethod.POST.equals(method)) { + + request.body(JSONUtil.toJsonStr(params)); + } + } + + addConfig(request, config); + + HttpResponse httpResponse = request.execute(); + return httpResponse.body(); + } +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/OkhttpExecutorAdapter.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/OkhttpExecutorAdapter.java new file mode 100644 index 00000000..59b92ab8 --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/OkhttpExecutorAdapter.java @@ -0,0 +1,17 @@ +package com.quick.api.invoker.executor; + +import com.quick.api.invoker.enums.HttpMethod; +import com.quick.api.invoker.executor.bo.ExecutorConfig; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +public class OkhttpExecutorAdapter extends AbstractExecutorAdapter{ + + + @Override + public String execute(String url, HttpMethod method, Map querys, ExecutorConfig config) { + return null; + } +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/bo/ExecutorConfig.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/bo/ExecutorConfig.java new file mode 100644 index 00000000..646fde2b --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/executor/bo/ExecutorConfig.java @@ -0,0 +1,17 @@ +package com.quick.api.invoker.executor.bo; + +import lombok.Data; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.util.Map; + +@Data +public class ExecutorConfig { + private int timeout = 5000; + private Map header; + + // Proxy proxy1 = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxy, 80)); + + private Proxy proxy; +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/model/AdapterClassModel.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/model/AdapterClassModel.java new file mode 100644 index 00000000..996d83b5 --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/model/AdapterClassModel.java @@ -0,0 +1,24 @@ +package com.quick.api.invoker.model; + +import lombok.Data; + +@Data +public class AdapterClassModel { + private int id; + private String className; + + private String classFqn; + + private String classContent; + + /** + * 1: executor,2: transform-request,3: transform-response + */ + private int adapterType; + + /** + * 1: 代码内部加载,2: 实时加载 + * 为2时,classContent肯定有值 + */ + private int loadType = 1; +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/service/ServerInvokerService.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/service/ServerInvokerService.java new file mode 100644 index 00000000..be9c877a --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/service/ServerInvokerService.java @@ -0,0 +1,10 @@ +package com.quick.api.invoker.service; + +import org.springframework.stereotype.Service; + +@Service +public class ServerInvokerService { + + + +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/transform/AbstractTransformAdapter.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/transform/AbstractTransformAdapter.java new file mode 100644 index 00000000..d81c5baa --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/transform/AbstractTransformAdapter.java @@ -0,0 +1,7 @@ +package com.quick.api.invoker.transform; + +import com.quick.api.invoker.Adapter; + +public abstract class AbstractTransformAdapter implements Adapter { + protected abstract String conversion(String source); +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/transform/request/DefaultRequestTransformAdapter.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/transform/request/DefaultRequestTransformAdapter.java new file mode 100644 index 00000000..1a9793eb --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/transform/request/DefaultRequestTransformAdapter.java @@ -0,0 +1,13 @@ +package com.quick.api.invoker.transform.request; + +import com.quick.api.invoker.transform.AbstractTransformAdapter; +import org.springframework.stereotype.Component; + +@Component +public class DefaultRequestTransformAdapter extends AbstractTransformAdapter { + + @Override + protected String conversion(String source) { + return null; + } +} diff --git a/quick-api-invoker/src/main/java/com/quick/api/invoker/transform/response/DefaultResponseTransformAdapter.java b/quick-api-invoker/src/main/java/com/quick/api/invoker/transform/response/DefaultResponseTransformAdapter.java new file mode 100644 index 00000000..ac9359e1 --- /dev/null +++ b/quick-api-invoker/src/main/java/com/quick/api/invoker/transform/response/DefaultResponseTransformAdapter.java @@ -0,0 +1,12 @@ +package com.quick.api.invoker.transform.response; + +import com.quick.api.invoker.transform.AbstractTransformAdapter; +import org.springframework.stereotype.Component; + +@Component +public class DefaultResponseTransformAdapter extends AbstractTransformAdapter { + @Override + protected String conversion(String source) { + return null; + } +} diff --git a/quick-api-invoker/src/test/java/TestMain.java b/quick-api-invoker/src/test/java/TestMain.java new file mode 100644 index 00000000..a65f8385 --- /dev/null +++ b/quick-api-invoker/src/test/java/TestMain.java @@ -0,0 +1,98 @@ +import cn.hutool.http.HttpUtil; +import com.quick.api.invoker.AdapterContextFactor; +import com.quick.api.invoker.Main; +import com.quick.api.invoker.enums.HttpMethod; +import com.quick.api.invoker.executor.AbstractExecutorAdapter; +import com.quick.api.invoker.executor.bo.ExecutorConfig; +import com.quick.api.invoker.model.AdapterClassModel; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Main.class) +@Slf4j +public class TestMain { + + + @Test + public void getTest() throws ClassNotFoundException { + AbstractExecutorAdapter executorAdapter = (AbstractExecutorAdapter) AdapterContextFactor.getAdapter("HttpExecutorAdapter", Class.forName("com.quick.api.invoker.executor.HttpExecutorAdapter")); + String host = "https://getweather.market.alicloudapi.com"; + String path = "/lundear/weather1d"; + String targetUrl = host + path; + + ExecutorConfig config = new ExecutorConfig(); + Map headers = new HashMap(); + //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105 + String appcode = "8852ff1c52e84e7595d1425bc99813c2"; + headers.put("Authorization", "APPCODE " + appcode); + + Map querys = new HashMap<>(); + querys.put("areaCode", "370100"); + querys.put("areaCn", "西安"); + querys.put("ip", "ip"); + querys.put("lng", "lng"); + querys.put("lat", "lat"); + querys.put("needalarm", "needalarm"); + querys.put("need3hour", "need3hour"); + querys.put("needIndex", "needIndex"); + querys.put("needObserve", "needObserve"); + + config.setHeader(headers); + + + String resp = executorAdapter.execute(targetUrl, HttpMethod.GET, querys, config); + System.out.println(resp); + + } + + + @Test + public void postTest() throws ClassNotFoundException { + + AdapterClassModel adapterClassModel = new AdapterClassModel(); + adapterClassModel.setClassFqn("com.quick.api.invoker.executor.HttpExecutorAdapter"); + adapterClassModel.setClassName("HttpExecutorAdapter"); + adapterClassModel.setLoadType(1); + adapterClassModel.setAdapterType(1); + +// AbstractExecutorAdapter executorAdapter = (AbstractExecutorAdapter) AdapterContextFactor.getAdapter("HttpExecutorAdapter", Class.forName("com.quick.api.invoker.executor.HttpExecutorAdapter")); + AbstractExecutorAdapter executorAdapter = AdapterContextFactor.getAdapter(adapterClassModel); + + + String host = "https://jisuhuilv.market.alicloudapi.com"; + String path = "/exchange/convert"; + String targetUrl = host + path; + String appcode = "8852ff1c52e84e7595d1425bc99813c2"; + Map headers = new HashMap(); + //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105 + headers.put("Authorization", "APPCODE " + appcode); + //根据API的要求,定义相对应的Content-Type + headers.put("Content-Type", "application/json; charset=UTF-8"); + Map param = new HashMap<>(); + param.put("amount", "10"); + param.put("from", "CNY"); + param.put("to", "USD"); + + targetUrl = HttpUtil.urlWithForm(targetUrl, param, Charset.defaultCharset(), true); + + ExecutorConfig config = new ExecutorConfig(); + config.setHeader(headers); + String resp = executorAdapter.execute(targetUrl, HttpMethod.GET, null, config); + + log.info("postTest: {}", resp); + } + + @Test + public void localJavaTest() throws ClassNotFoundException { + + } + +} diff --git a/quick-app/pom.xml b/quick-app/pom.xml new file mode 100644 index 00000000..a06b6c1f --- /dev/null +++ b/quick-app/pom.xml @@ -0,0 +1,67 @@ + + + quick-app + 1.0.0 + 4.0.0 + ${project.artifactId} + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.shiro + shiro-cas + provided + + + org.apache.shiro + shiro-core + provided + + + org.apache.shiro + shiro-spring + provided + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-test-autoconfigure + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.2.1.RELEASE + + + + + \ No newline at end of file diff --git a/quick-app/src/main/java/com/app/controller/AppController.java b/quick-app/src/main/java/com/app/controller/AppController.java new file mode 100644 index 00000000..7fb91f49 --- /dev/null +++ b/quick-app/src/main/java/com/app/controller/AppController.java @@ -0,0 +1,26 @@ +package com.app.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.UUID; + +@RestController("index") +public class AppController { + + @RequestMapping + public Object hello(HttpServletRequest request) { + String remoteHost = request.getRemoteHost(); + String html = "

Hello " + UUID.randomUUID().toString() + "!

" + + "Hostname: " + remoteHost + "
"; + + return html; + } + + @RequestMapping("/login") + public String myTranslate3(){ + return "login"; + } + +} diff --git a/quick-app/src/main/java/com/app/controller/Application.java b/quick-app/src/main/java/com/app/controller/Application.java new file mode 100644 index 00000000..ddfff44f --- /dev/null +++ b/quick-app/src/main/java/com/app/controller/Application.java @@ -0,0 +1,12 @@ +package com.app.controller; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = {"com.app"}) +//@EnableShiroConfig +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class); + } +} diff --git a/quick-app/src/main/java/com/app/controller/ShiroConfig.java b/quick-app/src/main/java/com/app/controller/ShiroConfig.java new file mode 100644 index 00000000..d4d01285 --- /dev/null +++ b/quick-app/src/main/java/com/app/controller/ShiroConfig.java @@ -0,0 +1,68 @@ +package com.app.controller; + +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.LinkedHashMap; + +@Configuration +public class ShiroConfig { + + /** + * 创建shiroFilterFactoryBean + */ + @Bean + public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ + ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); + //设置安全管理器 + shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); + + LinkedHashMap filterMap = new LinkedHashMap<>(); + filterMap.put("/**","authc"); +// filterMap.put("/update","authc"); + + //使用通配的方式设置某一个目录的访问级别 + /* filterMap.put("/templates","authc");*/ + + //修改访问被拦截过后的跳转页面 + shiroFilterFactoryBean.setLoginUrl("/login"); + shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); + + + return shiroFilterFactoryBean; + } + + + + + + /** + * 创建defaultWebSecurityManageer + */ + @Bean(value = "defaultWebSecurityManager") + public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + //关联我们创建的realm + securityManager.setRealm(userRealm); + return securityManager; + } + + + + + + + + + + /** + * 创建realm + */ + @Bean(value = "userRealm") + public UserRealm getRealm(){ + return new UserRealm(); + } +} \ No newline at end of file diff --git a/quick-app/src/main/java/com/app/controller/UserRealm.java b/quick-app/src/main/java/com/app/controller/UserRealm.java new file mode 100644 index 00000000..53f95505 --- /dev/null +++ b/quick-app/src/main/java/com/app/controller/UserRealm.java @@ -0,0 +1,34 @@ +package com.app.controller; + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; + +public class UserRealm extends AuthorizingRealm { + /** + * 认证 + * @param authenticationToken + * @return + * @throws AuthenticationException + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws + AuthenticationException { + System.out.println("执行了认证"); + return null; + } + + /** + * 授权 + * @param principalCollection + * @return + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { + System.out.println("指定了授权"); + return null; + } +} \ No newline at end of file diff --git a/quick-archetype/src/main/java/com/quick/controller/DepotController.java b/quick-archetype/src/main/java/com/quick/controller/DepotController.java new file mode 100644 index 00000000..c1335b30 --- /dev/null +++ b/quick-archetype/src/main/java/com/quick/controller/DepotController.java @@ -0,0 +1,21 @@ +package com.quick.controller; + + +import org.springframework.web.bind.annotation.RequestMapping; + +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 前端控制器 + *

+ * + * @author vector4wang + * @since 2023-10-17 + */ +@RestController +@RequestMapping("/depot-entity") +public class DepotController { + +} + diff --git a/quick-archetype/src/main/java/com/quick/entity/DepotEntity.java b/quick-archetype/src/main/java/com/quick/entity/DepotEntity.java new file mode 100644 index 00000000..984c6344 --- /dev/null +++ b/quick-archetype/src/main/java/com/quick/entity/DepotEntity.java @@ -0,0 +1,36 @@ +package com.quick.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * + *

+ * + * @author vector4wang + * @since 2023-10-17 + */ +@Getter +@Setter +@TableName("depot") +public class DepotEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @TableField("pid") + private Integer pid; + + @TableField("code") + private String code; + + +} diff --git a/quick-archetype/src/main/java/com/quick/mapper/DepotMapper.java b/quick-archetype/src/main/java/com/quick/mapper/DepotMapper.java new file mode 100644 index 00000000..b3555ef1 --- /dev/null +++ b/quick-archetype/src/main/java/com/quick/mapper/DepotMapper.java @@ -0,0 +1,16 @@ +package com.quick.mapper; + +import com.quick.entity.DepotEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author vector4wang + * @since 2023-10-17 + */ +public interface DepotMapper extends BaseMapper { + +} diff --git a/quick-archetype/src/main/java/com/quick/mapper/xml/DepotMapper.xml b/quick-archetype/src/main/java/com/quick/mapper/xml/DepotMapper.xml new file mode 100644 index 00000000..89b20935 --- /dev/null +++ b/quick-archetype/src/main/java/com/quick/mapper/xml/DepotMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + id, pid, code + + + diff --git a/quick-archetype/src/main/java/com/quick/service/DepotService.java b/quick-archetype/src/main/java/com/quick/service/DepotService.java new file mode 100644 index 00000000..8d6278f8 --- /dev/null +++ b/quick-archetype/src/main/java/com/quick/service/DepotService.java @@ -0,0 +1,16 @@ +package com.quick.service; + +import com.quick.entity.DepotEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 服务类 + *

+ * + * @author vector4wang + * @since 2023-10-17 + */ +public interface DepotService extends IService { + +} diff --git a/quick-archetype/src/main/java/com/quick/service/impl/DepotServiceImp.java b/quick-archetype/src/main/java/com/quick/service/impl/DepotServiceImp.java new file mode 100644 index 00000000..92e745ac --- /dev/null +++ b/quick-archetype/src/main/java/com/quick/service/impl/DepotServiceImp.java @@ -0,0 +1,20 @@ +package com.quick.service.impl; + +import com.quick.entity.DepotEntity; +import com.quick.mapper.DepotMapper; +import com.quick.service.DepotService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 服务实现类 + *

+ * + * @author vector4wang + * @since 2023-10-17 + */ +@Service +public class DepotServiceImp extends ServiceImpl implements DepotService { + +} diff --git a/quick-thread/pom.xml b/quick-async/pom.xml similarity index 52% rename from quick-thread/pom.xml rename to quick-async/pom.xml index 28d9178c..aa93e6b0 100644 --- a/quick-thread/pom.xml +++ b/quick-async/pom.xml @@ -2,10 +2,26 @@ - quick-thread - com.quick + quick-async 1.0-SNAPSHOT 4.0.0 + https://spring.io/guides/gs/async-method/ + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + org.springframework.boot + spring-boot-starter-web + + + + @@ -13,15 +29,8 @@ org.springframework.boot spring-boot-maven-plugin - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - + \ No newline at end of file diff --git a/quick-async/src/main/java/com/async/AsyncApplication.java b/quick-async/src/main/java/com/async/AsyncApplication.java new file mode 100644 index 00000000..e92631d3 --- /dev/null +++ b/quick-async/src/main/java/com/async/AsyncApplication.java @@ -0,0 +1,20 @@ +package com.async; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; + +/** + * Created with IDEA + * User: vector + * Data: 2018/5/28 0028 + * Time: 10:59 + * Description: + */ +@SpringBootApplication +@EnableAsync +public class AsyncApplication { + public static void main(String[] args) { + SpringApplication.run(AsyncApplication.class); + } +} diff --git a/quick-async/src/main/java/com/async/config/ThreadConfig.java b/quick-async/src/main/java/com/async/config/ThreadConfig.java new file mode 100644 index 00000000..10382800 --- /dev/null +++ b/quick-async/src/main/java/com/async/config/ThreadConfig.java @@ -0,0 +1,51 @@ +package com.async.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Created with IDEA + * User: vector + * Data: 2018/5/28 0028 + * Time: 11:01 + * Description: + */ +@Configuration +public class ThreadConfig { + + @Bean("asyncExecutor1") + public Executor asyncExecutor1() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); // 核心线程数10:线程池创建时候初始化的线程数 + executor.setMaxPoolSize(20); // 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程 + executor.setQueueCapacity(200); // 缓冲队列200:用来缓冲执行任务的队列 + executor.setKeepAliveSeconds(60); // 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁 + executor.setThreadNamePrefix("asyncExecutor1-"); // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池 + /** + * 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务 + */ + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } + + @Bean("asyncExecutor2") + public Executor asyncExecutor2() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); // 核心线程数10:线程池创建时候初始化的线程数 + executor.setMaxPoolSize(20); // 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程 + executor.setQueueCapacity(200); // 缓冲队列200:用来缓冲执行任务的队列 + executor.setKeepAliveSeconds(60); // 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁 + executor.setThreadNamePrefix("asyncExecutor2-"); // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池 + /** + * 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务 + */ + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } +} diff --git a/quick-async/src/main/java/com/async/entity/User.java b/quick-async/src/main/java/com/async/entity/User.java new file mode 100644 index 00000000..e62fd455 --- /dev/null +++ b/quick-async/src/main/java/com/async/entity/User.java @@ -0,0 +1,40 @@ +package com.async.entity; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown=true) +public class User { + + private String name; + private String blog; + + public User(String name, String blog) { + this.name = name; + this.blog = blog; + } + + public User() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBlog() { + return blog; + } + + public void setBlog(String blog) { + this.blog = blog; + } + + @Override + public String toString() { + return "User [name=" + name + ", blog=" + blog + "]"; + } + +} \ No newline at end of file diff --git a/quick-async/src/main/java/com/async/init/AppRunner.java b/quick-async/src/main/java/com/async/init/AppRunner.java new file mode 100644 index 00000000..bc011fe3 --- /dev/null +++ b/quick-async/src/main/java/com/async/init/AppRunner.java @@ -0,0 +1,55 @@ +package com.async.init; + +import com.async.entity.User; +import com.async.service.GitHubLookupService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +@Component +public class AppRunner implements CommandLineRunner { + + private static final Logger logger = LoggerFactory.getLogger(AppRunner.class); + + private final GitHubLookupService gitHubLookupService; + + public AppRunner(GitHubLookupService gitHubLookupService) { + this.gitHubLookupService = gitHubLookupService; + } + + @Override + public void run(String... args) throws Exception { + // Start the clock + long start = System.currentTimeMillis(); + + // Kick of multiple, asynchronous lookups + CompletableFuture page1 = gitHubLookupService.findUser("vector4wang"); + CompletableFuture page2 = gitHubLookupService.findUser("Zhuinden"); + CompletableFuture page3 = gitHubLookupService.findUser("xialeistudio"); + + Future futurePage1 = gitHubLookupService.findUser2("vector4wang"); + Future futurePage2 = gitHubLookupService.findUser2("Zhuinden"); + Future futurePage3 = gitHubLookupService.findUser2("xialeistudio"); + + logger.info("--> " + futurePage1.get()); + logger.info("--> " + futurePage2.get()); + logger.info("--> " + futurePage3.get()); + + // Wait until they are all done + CompletableFuture.allOf(page1,page2,page3).join(); + + // Print results, including elapsed time + logger.info("Elapsed time: " + (System.currentTimeMillis() - start)); + logger.info("--> " + page1.get()); + logger.info("--> " + page2.get()); + logger.info("--> " + page3.get()); + + + + } + +} \ No newline at end of file diff --git a/quick-async/src/main/java/com/async/service/GitHubLookupService.java b/quick-async/src/main/java/com/async/service/GitHubLookupService.java new file mode 100644 index 00000000..aab12eac --- /dev/null +++ b/quick-async/src/main/java/com/async/service/GitHubLookupService.java @@ -0,0 +1,57 @@ +package com.async.service; + +import com.async.entity.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.AsyncResult; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +@Service +public class GitHubLookupService { + + private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class); + + private final RestTemplate restTemplate; + + public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) { + this.restTemplate = restTemplateBuilder.build(); + } + + /** + * 不指定name的话,spring默认使用“SimpleAsyncTaskExecutor” + * 如果只自定义了一个Executor,则优先使用自定义的那个 + * 如果自定义了多个Executor 一定要在Async注解加上name,four则spring不知道你用的是哪一个,最终还是会使用SimpleAsyncTaskExecutor + * + * @param user + * @return + * @throws InterruptedException + */ + @Async("asyncExecutor2") + public CompletableFuture findUser(String user) throws InterruptedException { + logger.info(Thread.currentThread().getName() + " Looking up " + user); + String url = String.format("https://api.github.com/users/%s", user); + User results = restTemplate.getForObject(url, User.class); + // Artificial delay of 1s for demonstration purposes + Thread.sleep(1000L); + return CompletableFuture.completedFuture(results); + } + + @Async("asyncExecutor1") + public Future findUser2(String userName) { + logger.info(Thread.currentThread().getName() + " Looking up " + userName); + try { + Thread.sleep(new Random().nextInt(2000)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return new AsyncResult<>(new User("wangxc","xxxx")); + } + +} \ No newline at end of file diff --git a/quick-async/src/main/resources/application.properties b/quick-async/src/main/resources/application.properties new file mode 100644 index 00000000..a3ac65ce --- /dev/null +++ b/quick-async/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8080 \ No newline at end of file diff --git a/quick-batch/pom.xml b/quick-batch/pom.xml index 3231f43d..0a50dfd5 100644 --- a/quick-batch/pom.xml +++ b/quick-batch/pom.xml @@ -3,18 +3,15 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> quick-batch - com.quick 1.0-SNAPSHOT 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 1.3.1.RELEASE + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml - - 1.8 - @@ -31,7 +28,6 @@ mysql mysql-connector-java - 5.1.38 diff --git a/quick-batch/src/main/java/com/quick/batch/db/DatabaseConfig.java b/quick-batch/src/main/java/com/quick/batch/db/DatabaseConfig.java index 6827d19b..2ddf0570 100644 --- a/quick-batch/src/main/java/com/quick/batch/db/DatabaseConfig.java +++ b/quick-batch/src/main/java/com/quick/batch/db/DatabaseConfig.java @@ -1,7 +1,7 @@ package com.quick.batch.db; -import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; diff --git a/quick-batch/src/main/java/com/quick/batch/processor/RecordProcessor.java b/quick-batch/src/main/java/com/quick/batch/processor/RecordProcessor.java index 52d4263c..7a491327 100644 --- a/quick-batch/src/main/java/com/quick/batch/processor/RecordProcessor.java +++ b/quick-batch/src/main/java/com/quick/batch/processor/RecordProcessor.java @@ -17,7 +17,7 @@ public WriterSO process(RecordSO item) throws Exception { writerSo.setId(item.getId()); writerSo.setFullName(item.getFirstName() + " " + item.getLastName()); String substring = String.valueOf(Math.random()).substring(3, 8); - if(substring.contains("0")){ + if (substring.contains("0")) { return null; // 返回空,即词条不插入 } writerSo.setRandomNum(substring); diff --git a/quick-batch/src/main/resources/application.properties b/quick-batch/src/main/resources/application.properties index 065cebce..e7f0ef34 100644 --- a/quick-batch/src/main/resources/application.properties +++ b/quick-batch/src/main/resources/application.properties @@ -1,4 +1,4 @@ -spring.datasource.driverClassName=com.mysql.jdbc.Driver +spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/world spring.datasource.username=root spring.datasource.password=root123 diff --git a/quick-cache/pom.xml b/quick-cache/pom.xml new file mode 100644 index 00000000..06edf52c --- /dev/null +++ b/quick-cache/pom.xml @@ -0,0 +1,42 @@ + + + quick-cache + 1.0-SNAPSHOT + 4.0.0 + + + 启用spring缓存 + 1、如何使用 + 2、缓存如何工作 + 3、缓存的什么 + + 官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#cache + 参考:https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/index.html + + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + org.springframework.boot + spring-boot-starter-cache + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/quick-cache/src/main/java/com/quick/.DS_Store b/quick-cache/src/main/java/com/quick/.DS_Store new file mode 100644 index 00000000..0c889e21 Binary files /dev/null and b/quick-cache/src/main/java/com/quick/.DS_Store differ diff --git a/quick-cache/src/main/java/com/quick/cache/AppRunner.java b/quick-cache/src/main/java/com/quick/cache/AppRunner.java new file mode 100644 index 00000000..55131d9e --- /dev/null +++ b/quick-cache/src/main/java/com/quick/cache/AppRunner.java @@ -0,0 +1,56 @@ +package com.quick.cache; + +import com.quick.cache.entity.Book; +import com.quick.cache.repo.BookRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Component; + +@Component +public class AppRunner implements CommandLineRunner { + + private static final Logger logger = LoggerFactory.getLogger(AppRunner.class); + + private final BookRepository bookRepository; + + public AppRunner(BookRepository bookRepository) { + this.bookRepository = bookRepository; + } + + @Override + public void run(String... args) throws Exception { + logger.info(".... Fetching books"); + logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); + logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567")); + + logger.info("以上两个查询一定从数据库查询,再根据cacheenable中的条件确定是否缓存"); + + logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); + logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567")); + + logger.info("获取缓存中的数据,有的不需要查数据库"); + + + Book book = new Book("isbn-4567", "更新过的book",""); + bookRepository.update(book); + + logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); + logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567")); + + try { + bookRepository.clear(); + } catch (Exception e) { + e.printStackTrace(); + } + + + logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); + logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567")); + } + + + +} \ No newline at end of file diff --git a/quick-cache/src/main/java/com/quick/cache/CacheApplication.java b/quick-cache/src/main/java/com/quick/cache/CacheApplication.java new file mode 100644 index 00000000..9b4451d3 --- /dev/null +++ b/quick-cache/src/main/java/com/quick/cache/CacheApplication.java @@ -0,0 +1,13 @@ +package com.quick.cache; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; + +@SpringBootApplication +@EnableCaching +public class CacheApplication { + public static void main(String[] args) { + SpringApplication.run(CacheApplication.class, args); + } +} diff --git a/quick-cache/src/main/java/com/quick/cache/config/SelfCacheManager.java b/quick-cache/src/main/java/com/quick/cache/config/SelfCacheManager.java new file mode 100644 index 00000000..d38e227b --- /dev/null +++ b/quick-cache/src/main/java/com/quick/cache/config/SelfCacheManager.java @@ -0,0 +1,18 @@ +package com.quick.cache.config; + +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +import java.util.Collection; + +public class SelfCacheManager implements CacheManager { + @Override + public Cache getCache(String name) { + return null; + } + + @Override + public Collection getCacheNames() { + return null; + } +} diff --git a/quick-cache/src/main/java/com/quick/cache/entity/Book.java b/quick-cache/src/main/java/com/quick/cache/entity/Book.java new file mode 100644 index 00000000..addff75a --- /dev/null +++ b/quick-cache/src/main/java/com/quick/cache/entity/Book.java @@ -0,0 +1,43 @@ +package com.quick.cache.entity; + +public class Book { + + private String isbn; + private String title; + private String auto; + + public Book(String isbn, String title, String auto) { + this.isbn = isbn; + this.title = title; + this.auto = auto; + } + + public String getAuto() { + return auto; + } + + public void setAuto(String auto) { + this.auto = auto; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + @Override + public String toString() { + return "Book{" + "isbn='" + isbn + '\'' + ", title='" + title + '\'' + ", auto='" + auto + '\'' + '}'; + } +} \ No newline at end of file diff --git a/quick-cache/src/main/java/com/quick/cache/repo/BookRepository.java b/quick-cache/src/main/java/com/quick/cache/repo/BookRepository.java new file mode 100644 index 00000000..204cfb7b --- /dev/null +++ b/quick-cache/src/main/java/com/quick/cache/repo/BookRepository.java @@ -0,0 +1,15 @@ +package com.quick.cache.repo; + +import com.quick.cache.entity.Book; + +import java.util.List; + +public interface BookRepository { + + Book getByIsbn(String isbn); + + Book update(Book book); + + void clear(); + +} \ No newline at end of file diff --git a/quick-cache/src/main/java/com/quick/cache/repo/SimpleBookRepository.java b/quick-cache/src/main/java/com/quick/cache/repo/SimpleBookRepository.java new file mode 100644 index 00000000..9c1d5e5f --- /dev/null +++ b/quick-cache/src/main/java/com/quick/cache/repo/SimpleBookRepository.java @@ -0,0 +1,71 @@ +package com.quick.cache.repo; + +import com.quick.cache.AppRunner; +import com.quick.cache.entity.Book; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.interceptor.CacheOperation; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class SimpleBookRepository implements BookRepository { + + private static final Logger logger = LoggerFactory.getLogger(SimpleBookRepository.class); + + /** + * condition 在什么条件下 缓存结果 + * unless 除非结果怎么怎么样的时候被缓存 !#result.auto.equals('vector') 当结果的auto不等于vector的时候被缓存 + * unless Default is "", meaning that caching is never vetoed.(需要慢慢体会) 否的时候生效,真的时候失效 + * https://docs.spring.io/spring/docs/5.0.7.RELEASE/javadoc-api/ + * @param isbn + * @return + */ + @Override + @Cacheable(cacheNames = "books", key = "#isbn.hashCode()", condition = "#isbn.equals('isbn-4567')", unless = "!#result.auto.equals('vector')") + public Book getByIsbn(String isbn) { + simulateSlowService(); + return new Book(isbn, "Some book", "vector"); + } + + /** + * 最好指定key,此时会根据key将cache中的内容更新为最新 + * @param book + * @return + */ + @Override + @CachePut(cacheNames = "books", key = "#book.isbn.hashCode()") // 方法的返回值会被更新到缓存中 + public Book update(Book book) { + logger.info("更新book"); + return book; + } + + /** + * beforeInvocation = true 意味着不管业务如何出错,缓存已清空 + * beforeInvocation = false 等待方法处理完之后再清空缓存,缺点是如果逻辑出异常了,会导致缓存不会被清空 + */ + @Override + @CacheEvict(cacheNames = "books", allEntries = true, beforeInvocation = true) + public void clear() { + logger.warn("清空books缓存数据"); + throw new RuntimeException("测试 beforeInvocation = fasle"); + } + + + /** + * 模拟慢查询 + */ + private void simulateSlowService() { + try { + long time = 3000L; + Thread.sleep(time); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + +} \ No newline at end of file diff --git a/quick-camunda/pom.xml b/quick-camunda/pom.xml new file mode 100644 index 00000000..49b5b9c3 --- /dev/null +++ b/quick-camunda/pom.xml @@ -0,0 +1,74 @@ + + + quick-camunda + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + 8 + 8 + 6.4.0 + + + + + org.springframework.boot + spring-boot-starter + + + + + + + + + + + + + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.flowable + flowable-spring-boot-starter + ${flowable.version} + + + + com.h2database + h2 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quick-camunda/src/main/java/com/quick/flowable/FlowableApplication.java b/quick-camunda/src/main/java/com/quick/flowable/FlowableApplication.java new file mode 100644 index 00000000..8c4fffb4 --- /dev/null +++ b/quick-camunda/src/main/java/com/quick/flowable/FlowableApplication.java @@ -0,0 +1,34 @@ +package com.quick.flowable; + +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +/** + * + * @author wangxc + * @date: 2022/7/20 22:33 + * + */ +@SpringBootApplication +public class FlowableApplication { + public static void main(String[] args) { + SpringApplication.run(FlowableApplication.class, args); + } + + @Bean + public CommandLineRunner init(final RepositoryService repositoryService, final RuntimeService runtimeService, + final TaskService taskService) { + return strings -> { + System.out.println( + "Number of process definitions : " + repositoryService.createProcessDefinitionQuery().count()); + System.out.println("Number of tasks : " + taskService.createTaskQuery().count()); + runtimeService.startProcessInstanceByKey("oneTaskProcess"); + System.out.println("Number of tasks after process start: " + taskService.createTaskQuery().count()); + }; + } +} diff --git a/quick-camunda/src/main/resources/processes/one-task-process.bpmn20.xml b/quick-camunda/src/main/resources/processes/one-task-process.bpmn20.xml new file mode 100644 index 00000000..f969206d --- /dev/null +++ b/quick-camunda/src/main/resources/processes/one-task-process.bpmn20.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/quick-config-encrypt/README.md b/quick-config-encrypt/README.md new file mode 100644 index 00000000..83e0bdac --- /dev/null +++ b/quick-config-encrypt/README.md @@ -0,0 +1,4 @@ +# 配置文件加解密 +使用见测试用例 + +其他内容,参见https://github.com/ulisesbocchio/jasypt-spring-boot \ No newline at end of file diff --git a/quick-config-encrypt/pom.xml b/quick-config-encrypt/pom.xml new file mode 100644 index 00000000..6bb2a434 --- /dev/null +++ b/quick-config-encrypt/pom.xml @@ -0,0 +1,41 @@ + + + quick-config-encrypt + 1.0-SNAPSHOT + 4.0.0 + + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + org.springframework.boot + spring-boot-starter-web + + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/quick-config-encrypt/src/main/java/com/quick/encrypt/EncryptApplication.java b/quick-config-encrypt/src/main/java/com/quick/encrypt/EncryptApplication.java new file mode 100644 index 00000000..1de54403 --- /dev/null +++ b/quick-config-encrypt/src/main/java/com/quick/encrypt/EncryptApplication.java @@ -0,0 +1,15 @@ +package com.quick.encrypt; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author vector + * @date: 2018/11/9 0009 16:30 + */ +@SpringBootApplication +public class EncryptApplication { + public static void main(String[] args) { + SpringApplication.run(EncryptApplication.class, args); + } +} diff --git a/quick-config-encrypt/src/main/java/com/quick/encrypt/service/BizService.java b/quick-config-encrypt/src/main/java/com/quick/encrypt/service/BizService.java new file mode 100644 index 00000000..6c9b4c2a --- /dev/null +++ b/quick-config-encrypt/src/main/java/com/quick/encrypt/service/BizService.java @@ -0,0 +1,14 @@ +package com.quick.encrypt.service; + +import org.springframework.stereotype.Service; + +/** + * @author vector + * @date: 2018/11/9 0009 16:33 + */ +@Service +public class BizService { + + + +} diff --git a/quick-config-encrypt/src/main/resources/application.yml b/quick-config-encrypt/src/main/resources/application.yml new file mode 100644 index 00000000..6d9413a8 --- /dev/null +++ b/quick-config-encrypt/src/main/resources/application.yml @@ -0,0 +1,8 @@ +jasypt: + encryptor: + password: e!Jd&ljyJ^e4I5oU + + +app: + key: ENC(EeLEEUGhQ+bXIoVs9bItmA==) + name: \ No newline at end of file diff --git a/quick-config-encrypt/src/test/java/com/encrypt/TestMain.java b/quick-config-encrypt/src/test/java/com/encrypt/TestMain.java new file mode 100644 index 00000000..8c331b93 --- /dev/null +++ b/quick-config-encrypt/src/test/java/com/encrypt/TestMain.java @@ -0,0 +1,31 @@ +package com.encrypt; + +import com.quick.encrypt.EncryptApplication; +import org.jasypt.encryption.StringEncryptor; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author vector + * @date: 2018/11/9 0009 16:43 + */ +@SpringBootTest(classes = EncryptApplication.class) +@RunWith(SpringJUnit4ClassRunner.class) +public class TestMain { + @Autowired + StringEncryptor stringEncryptor;//密码解码器自动注入 + + @Value("${app.key}") + private String key; + + @Test + public void test() { + String encrypt = stringEncryptor.decrypt("Er1K3WxM3M8o2Ynazkwkbg=="); + System.out.println(encrypt);//Er1K3WxM3M8o2Ynazkwkbg== + System.out.println(key); + } +} diff --git a/quick-config-server/ReadMe.md b/quick-config-server/ReadMe.md new file mode 100644 index 00000000..15366885 --- /dev/null +++ b/quick-config-server/ReadMe.md @@ -0,0 +1,32 @@ +### 各配置中心比较 +https://www.cnblogs.com/xiaoqi/p/configserver-compair.html + +## Apollo + +### 部署 +推荐手动部署,刚开始我用的是docker,后面总是pull不到配置,再后来改成手动部署 +什么问题没有,一把过~ + +一定要看准版本号,网上浏览别人的使用过程,版本号几乎没见到过一样的,所以一定要确保当前使用的跟部署的版本号一致 + +`https://github.com/ctripcorp/apollo/wiki/Quick-Start` +```text +用户名apollo,密码admin +``` + + + +### namespace 核心 +https://github.com/ctripcorp/apollo/wiki/Apollo%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E4%B9%8B%E2%80%9CNamespace%E2%80%9D + +#### 部署配置中心 +本地多环境部署成功案例 +https://blog.csdn.net/qq_15070281/article/details/83583052 + + +### 经典案例 +https://github.com/ctripcorp/apollo-use-cases/blob/master/README.md + + + + diff --git a/quick-config-server/pom.xml b/quick-config-server/pom.xml new file mode 100644 index 00000000..4d949415 --- /dev/null +++ b/quick-config-server/pom.xml @@ -0,0 +1,42 @@ + + + quick-config-server + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + UTF-8 + + + + org.springframework.boot + spring-boot-starter-parent + 2.0.4.RELEASE + + + + + + org.springframework.boot + spring-boot-starter + + + + + com.ctrip.framework.apollo + apollo-client + 1.5.1 + + + + org.apache.commons + commons-lang3 + 3.8.1 + + + + + \ No newline at end of file diff --git a/quick-config-server/src/main/java/com/disconfig/DisconfigApplication.java b/quick-config-server/src/main/java/com/disconfig/DisconfigApplication.java new file mode 100644 index 00000000..9b8edb1f --- /dev/null +++ b/quick-config-server/src/main/java/com/disconfig/DisconfigApplication.java @@ -0,0 +1,45 @@ +package com.disconfig; + +import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * 配置VM参数-Denv=DEV + * @author wangxc + * @date: 2019/12/12 下午9:59 + * + */ +@SpringBootApplication +@EnableApolloConfig +public class DisconfigApplication implements CommandLineRunner { + + @Value("${msg}") + private String msg; + + @Value("${redis.port:100}") + private int redisPort; + + + public static void main(String[] args) { + SpringApplication.run(DisconfigApplication.class); + + } + + @Override + public void run(String... args) throws Exception { + Executors.newSingleThreadExecutor().submit(() -> { + while (true) { + System.out.println("启动完成:" + msg); + System.out.println("redisPort: " + redisPort); + TimeUnit.SECONDS.sleep(1); + } + }); + + } +} diff --git a/quick-config-server/src/main/resources/application.properties b/quick-config-server/src/main/resources/application.properties new file mode 100644 index 00000000..ac585a72 --- /dev/null +++ b/quick-config-server/src/main/resources/application.properties @@ -0,0 +1,14 @@ +#app.id=spring-boot-logger +app.id=sb-log +# set apollo meta server address, adjust to actual address if necessary +apollo.meta=http://localhost:8081 +# 集群 +#apollo.cluster=SomeCluster +# 自定义本地配置文件缓存路径 +apollo.cacheDir=/opt/data/quick-apollo-config +#apollo. +# 设置在应用启动阶段就加载 Apollo 配置 +apollo.bootstrap.enabled=true +apollo.bootstrap.eagerLoad.enabled=true +# 注入 application namespace +apollo.bootstrap.namespaces=application diff --git a/quick-container/.DS_Store b/quick-container/.DS_Store new file mode 100644 index 00000000..ee771938 Binary files /dev/null and b/quick-container/.DS_Store differ diff --git a/quick-container/README.md b/quick-container/README.md new file mode 100644 index 00000000..cdaf02a5 --- /dev/null +++ b/quick-container/README.md @@ -0,0 +1,291 @@ +![DOCKER.png](http://upload-images.jianshu.io/upload_images/3167229-9a4b70bb7a4dd06b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +2022年03月02日22:57:14 更新 + +使用docker-compose 完成服务编排,并支持依赖启动 +结合assembly完成如下目录 +``` +lib +conf +bin +logs +``` +这四个目录都可以通过docker-compose 宿主机与容器的映射,从而实现快速的更新、快速的查看日志等功能 +注:vector4wang/java:8 增加了nc工具 + + + + + + +--- +2022年03月01日22:44:45 更新 + +使用docker-compose 编排服务,支持依赖启动 +参考:https://www.cnblogs.com/wang_yb/p/9400291.html +``` +docker run -itd --name redis-test -p 6379:6379 redis +``` + +--- +2022年03月01日 更新 + +需要开启docker远程连接 + +mac版操作:https://github.com/docker/for-mac/issues/770 + +--- +2022年2月28日 更新 +对java应用的docker 进行'标准化'建设,增加starts.sh启动脚本,使用assembly打标准包,即所有的jar包存放在lib下,conf用于外部挂载配置文件 +```text +## 注意,以下命令防止容器退出 +tailf /dev/null +``` + +--- + +> 准备工作 + +- 会一点springboot +不会没关系,花十几分钟补一下[Quick-SpringBoot](https://github.com/vector4wang/spring-boot-quick) +- 会一点Maven +不会没关系,花几分钟补一下[Maven的快速应用](http://blog.csdn.net/column/details/16112.html) +- 会一点Linux命令 +不会没关系,花十几分再补一下[Linux菜鸟教程](http://www.runoob.com/linux/linux-tutorial.html) +- 一台联网的Centos机器 + +> 安装Docker + +进入Centos终端,执行命令 +```bash +yum install docker +``` +静待一会,等待安装成功,之后再执行 +```bash +systemctl start docker +``` +然后查看安装的docker版本 +```bash +[root@iZ2zejaebtdc3kosrltpqaZ ~]# docker version +Client: + Version: 1.12.6 + API version: 1.24 + Package version: docker-1.12.6-68.gitec8512b.el7.centos.x86_64 + Go version: go1.8.3 + Git commit: ec8512b/1.12.6 + Built: Mon Dec 11 16:08:42 2017 + OS/Arch: linux/amd64 + +Server: + Version: 1.12.6 + API version: 1.24 + Package version: docker-1.12.6-68.gitec8512b.el7.centos.x86_64 + Go version: go1.8.3 + Git commit: ec8512b/1.12.6 + Built: Mon Dec 11 16:08:42 2017 + OS/Arch: linux/amd64 +``` +> 准备Springboot项目 + +简单的创建一个工程,实现一个接口即可,然后在pom中添加docker插件,相关代码如下 +```java +@SpringBootApplication +@RestController +public class DockerApplication { + @RequestMapping("/hello") + public String hello() { + return "

Hello Spring-Boot Maven Docker

"; + } + + public static void main(String[] args) { + SpringApplication.run(DockerApplication.class); + } +} +``` +```xml + + + + org.springframework.boot + spring-boot-maven-plugin + + + com.spotify + docker-maven-plugin + 0.4.3 + + ${docker.image.prefix}/${project.artifactId} + src/main/docker + + + / + ${project.build.directory} + ${project.build.finalName}.jar + + + + + + +``` +再创建一个Dockerfile,**注意里面的`ADD xxx.jar`是你使用maven打包之后的jar包的名称,其他的不变** +```bash +FROM frolvlad/alpine-oraclejdk8:slim +VOLUME /tmp +ADD quick-docker-1.0-SNAPSHOT.jar app.jar +RUN sh -c 'touch /app.jar' +ENV JAVA_OPTS="" +ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] +``` +最终的目录结构如下 +```bash +. +├── pom.xml +└── src + └── main + ├── docker + │   └── Dockerfile + ├── java + │   └── com + │   └── docker + │   └── DockerApplication.java + └── resources + └── application.properties +``` +你也可以直接下载源码https://github.com/vector4wang/spring-boot-quick/tree/master/quick-docker +运行的效果如下 +[![1.png](http://upload-images.jianshu.io/upload_images/3167229-61d8c78770673536.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://i.loli.net/2018/01/18/5a60a90700a46.png) + +> 使用Docker部署服务 + +将src文件和pom放在任意文件夹下,执行命令 +```bash +mvn package docker:build +``` +在打包的过程中比以往多了以下几步 +```log +[INFO] Building image vector4wang/quick-docker +Step 1 : FROM frolvlad/alpine-oraclejdk8:slim +Trying to pull repository docker.io/frolvlad/alpine-oraclejdk8 ... +Pulling from docker.io/frolvlad/alpine-oraclejdk8 +ff3a5c916c92: Pull complete +51d82ddde372: Pull complete +31b0b3ea6b23: Pull complete +Digest: sha256:52864f95ade9d007fb439f9d396742a104dc1067d32b6837caf6df1e0f1a5dc1 + ---> 7d711dabe19e +Step 2 : VOLUME /tmp + ---> Running in 04ce9f4dce7d + ---> f46d6639a6f3 +Removing intermediate container 04ce9f4dce7d +Step 3 : ADD quick-docker-1.0-SNAPSHOT.jar app.jar + ---> 1540566b402c +Removing intermediate container e8139d4f64e5 +Step 4 : RUN sh -c 'touch /app.jar' + ---> Running in ae7be1b36370 + ---> 49a6d6c256ac +Removing intermediate container ae7be1b36370 +Step 5 : ENV JAVA_OPTS "" + ---> Running in ea06fa1e72ff + ---> af945e1fd8b3 +Removing intermediate container ea06fa1e72ff +Step 6 : ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -jar /app.jar + ---> Running in a1b9bed3a100 + ---> d1dcc0ee4b44 +Removing intermediate container a1b9bed3a100 +Successfully built d1dcc0ee4b44 +[INFO] Built vector4wang/quick-docker +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 36.315 s +[INFO] Finished at: 2018-01-18T21:58:53+08:00 +[INFO] Final Memory: 32M/77M +[INFO] ------------------------------------------------------------------------ +[root@iZ2zejaebtdc3kosrltpqaZ code]# +``` +> Docker启动服务 + +先来看下服务部署完之后,docker里多了些什么吧,执行`docker images` +[![2.png](http://upload-images.jianshu.io/upload_images/3167229-10f9e4d48f65b86d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://i.loli.net/2018/01/18/5a60ab26e3b5b.png) + +简单的看到了docker中存在的镜像、标签、镜像ID、已经创建的时间和大小,看下`vector4wang/quick-docker` 这个是在pom中`${docker.image.prefix}/${project.artifactId}`配置的,比较重要,因为它和接下来要讲的将镜像提交到DockerHub有着密切的联系。 +执行`docker run -d -p 8080:8080 vector4wang/quick-docker` 来启动服务。 +简单解释下这个命令 + +- -d 代表后台运行 +- -p 标识宿主机与docker服务的端口映射,注意谁前谁后:【**宿主端口:docker内服务端口**】 +- vector4wang/quick-docker 就是启动镜像的名称,当然了使用IMAGE ID 也是可以的 + +好了,看下docker是否将服务启动成功,执行`docker ps` +[![3.png](http://upload-images.jianshu.io/upload_images/3167229-dbce8f9acf01a030.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://i.loli.net/2018/01/18/5a60ac0d680ea.png) +很顺利,运行成功了 + +- `CONTAINER ID` 容器ID +- `PORTS`宿主与docker内部的服务映射 +- `NAMES` 容器名称,跟容器ID对应,如果你不指定名称的话,docker会随机给你分配一个name, 名字还是很有意思的 +接下来,就是验证的时候了,在浏览器输入对应URL +[![4.png](http://upload-images.jianshu.io/upload_images/3167229-49d4ab8badd306bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://i.loli.net/2018/01/18/5a60ad778b7d1.png) +到此,docker部署springboot服务的过程就结束了,接下来就是将自己开发的镜像推送到DockerHub + +> DockerHub + +推送镜像通俗的说就是类似把代码推送到github一样,这个是把一个完整的应用程序推送到[DockerHub](https://hub.docker.com),供其他Docker pull与使用。 +首先你需要注册一个账号,很简单不再赘述 +创建仓库的时候,回头看看pom里的配置,两者有着密切的联系 +[![5.png](http://upload-images.jianshu.io/upload_images/3167229-9239c12859480cc2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://i.loli.net/2018/01/18/5a60afadb4232.png) +这个你可以理解成maven里面的依赖jar包的“坐标” +回到终端,执行`docker login` 按照提示输入用户名和密码 +```bash +[root@iZ2zejaebtdc3kosrltpqaZ code]# docker login +Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. +Username (vector4wang): vector4wang +Password: +Login Succeeded +[root@iZ2zejaebtdc3kosrltpqaZ code]# +``` +接下来就是推送的了,执行命令` docker push vector4wang/quick-docker:latest` +latest是tag,相当于版本号 +包比较大网络传输的比较慢,等了几分钟过程如下(如果失败就多重试几次) +[![6.png](http://upload-images.jianshu.io/upload_images/3167229-62a41613540b250d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://i.loli.net/2018/01/18/5a60b1310858e.png) +再来看看DockerHub页面 +[![7.png](http://upload-images.jianshu.io/upload_images/3167229-ff3a4d6a00f494b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://i.loli.net/2018/01/18/5a60b1e3e5e61.png) +注意右边红框的代码,意思就是说,在任何docker执行 +```bash +docker pull vector4wang/quick-docker +``` +就能快速部署并启动一个应用,网络快的话,能达到秒级别的 + +> 快速部署 + +现在来感受下docker的强大之处 +我把docker所有容器和镜像清空,快速的去部署quick-docker服务 +[![演示.gif](http://upload-images.jianshu.io/upload_images/3167229-9a41b47f302cbde7.gif?imageMogr2/auto-orient/strip)](https://i.loli.net/2018/01/18/5a60b4f5eb2e1.gif) +注意看右下角时间,刚刚好一分钟~~~~ + +> 最后 + +Docker的强大之处你也见识了,真的是再一次解放了程序员,让程序员不再关心如何去配置项目需要的环境。接下来我会持续记录学习和使用Docker的过程,并从以下几个问题着手: + +- 什么是Docker +- Docker常用命令 +- Image和Continer两者的关系 +- Dockerfile的应用 +- 容器的生命周期 +- Docker内部服务的监控与性能调优 +- 。。。 + + + + +CSDN:http://blog.csdn.net/qqhjqs?viewmode=list + +博客:http://blog.wangxc.club + +简书:https://www.jianshu.com/u/223a1314e818 + +Github:https://github.com/vector4wang + +Gitee:https://gitee.com/backwxc + +如果感觉有帮助的话,点个赞哦~ diff --git a/quick-container/pom.xml b/quick-container/pom.xml new file mode 100644 index 00000000..e89e34cb --- /dev/null +++ b/quick-container/pom.xml @@ -0,0 +1,88 @@ + + + quick-container + 1.0-SNAPSHOT + 4.0.0 + + + vector4wang + + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.projectlombok + lombok + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-assembly-plugin + + false + + + + make-assembly + package + + single + + + false + + src/main/assembly/assembly.xml + + ${project.artifactId} + + + + + + com.spotify + docker-maven-plugin + + ${docker.image.prefix}/${project.artifactId} + src/main/assembly + + + / + ${project.build.directory} + + + + + + + + + \ No newline at end of file diff --git a/quick-container/src/main/assembly/Dockerfile b/quick-container/src/main/assembly/Dockerfile new file mode 100644 index 00000000..44d99b90 --- /dev/null +++ b/quick-container/src/main/assembly/Dockerfile @@ -0,0 +1,6 @@ +FROM java:8 +COPY *.zip /app/app.zip +RUN unzip -o /app/app.zip -d /app/ && rm -rf /app/app.zip + +WORKDIR /app/ +CMD ["sh","bin/start.sh"] \ No newline at end of file diff --git a/quick-container/src/main/assembly/assembly.xml b/quick-container/src/main/assembly/assembly.xml new file mode 100644 index 00000000..b33a81f9 --- /dev/null +++ b/quick-container/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + assembly-${env.devMode} + + zip + + false + + + src/main/assembly/bin + bin + 0755 + 0755 + + + src/main/assembly/conf + conf + 0744 + 0644 + + + lib + lib + 0744 + 0644 + + + + + lib + + + \ No newline at end of file diff --git a/quick-container/src/main/assembly/bin/start.bat b/quick-container/src/main/assembly/bin/start.bat new file mode 100644 index 00000000..e69de29b diff --git a/quick-container/src/main/assembly/bin/start.sh b/quick-container/src/main/assembly/bin/start.sh new file mode 100644 index 00000000..f89b9a65 --- /dev/null +++ b/quick-container/src/main/assembly/bin/start.sh @@ -0,0 +1,102 @@ +#!/bin/bash +log_file=log."$(date +%Y%m%d%H%M%S)" +exec 1>>$log_file +cd `dirname $0` +BIN_DIR=`pwd` +cd .. +DEPLOY_DIR=`pwd` +CONF_DIR=$DEPLOY_DIR/conf +LOGS_DIR=$DEPLOY_DIR/logs + +## 获取项目名 +SERVER_NAME='test-app' + +#SERVER_NAME=`sed '/disconf.app/!d;s/.*=//' conf/disconf.properties | tr -d '\r'` +#SERVER_NAME=`sed '/dubbo.application.name/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` +#SERVER_PROTOCOL=`sed '/dubbo.protocol.name/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` +#SERVER_PORT=`sed '/dubbo.protocol.port/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` +#LOGS_FILE=`sed '/dubbo.log4j.file/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` + + +echo "project name:$SERVER_NAME" +#echo "server protocol:$SERVER_PROTOCOL" +#echo "server port:$SERVER_PORT" + +if [ -z "$SERVER_NAME" ]; then + SERVER_NAME=`hostname` +fi + +PIDS=`ps aux | grep java | grep "$CONF_DIR" |awk '{print $2}'` +if [ -n "$PIDS" ]; then + echo "ERROR: The $SERVER_NAME already started!" + echo "PID: $PIDS" + exit 1 +fi + +#if [ -n "$SERVER_PORT" ]; then +# SERVER_PORT_COUNT=`netstat -tln | grep $SERVER_PORT | wc -l` +# if [ $SERVER_PORT_COUNT -gt 0 ]; then +# echo "ERROR: The $SERVER_NAME port $SERVER_PORT already used!" +# exit 1 +# fi +#fi + +#LOGS_DIR="" +#if [ -n "$LOGS_FILE" ]; then +# LOGS_DIR=`dirname $LOGS_FILE` +#else +# LOGS_DIR=$DEPLOY_DIR/logs +#fi + +if [ ! -d $LOGS_DIR ]; then + mkdir $LOGS_DIR +fi +STDOUT_FILE=$LOGS_DIR/stdout.log + +LIB_DIR=$DEPLOY_DIR/lib +LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"` + +#JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true " +JAVA_DEBUG_OPTS="" +if [ "$1" = "debug" ]; then + JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n " +fi +JAVA_JMX_OPTS="" +if [ "$1" = "jmx" ]; then + JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " +fi +JAVA_MEM_OPTS="" +if [ ! -n "$JAVA_OPTS" ]; then + BITS=`java -version 2>&1 | grep -i 64-bit` + if [ -n "$BITS" ]; then + JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 " + else + JAVA_MEM_OPTS=" -server -Xms1g -Xmx1g -XX:PermSize=128m -XX:SurvivorRatio=2 -XX:+UseParallelGC " + fi +fi + +echo -e "Starting the $SERVER_NAME ...\c" +#nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS com.alibaba.dubbo.container.Main > $STDOUT_FILE 2>&1 & +nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS com.docker.DockerApplication > $STDOUT_FILE 2>&1 & + + +echo "wait the server starting..." + +COUNT=0 +while [ $COUNT -lt 1 ]; do + echo -e ".\c" + sleep 1 + COUNT=`ps aux | grep java | grep "$DEPLOY_DIR" | awk '{print $2}' | wc -l` + if [ $COUNT -gt 0 ]; then + break + fi +done + +echo "OK!" +PIDS=`ps aux | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'` +echo "PID: $PIDS" +echo "STDOUT: $STDOUT_FILE" + + +## 注意,以下命令防止容器退出 +tailf /dev/null \ No newline at end of file diff --git a/quick-container/src/main/assembly/conf/application.properties b/quick-container/src/main/assembly/conf/application.properties new file mode 100644 index 00000000..879afa0d --- /dev/null +++ b/quick-container/src/main/assembly/conf/application.properties @@ -0,0 +1,11 @@ +server.port=8080 + +spring.redis.database=1 +spring.redis.host=redis +spring.redis.port=6379 +spring.redis.password= +spring.redis.pool.max-active=8 +spring.redis.pool.max-wait=-1 +spring.redis.pool.max-idle=8 +spring.redis.pool.min-idle=0 +spring.redis.timeout=500 \ No newline at end of file diff --git a/quick-container/src/main/dockercompose/docker-compose.yaml b/quick-container/src/main/dockercompose/docker-compose.yaml new file mode 100644 index 00000000..8a41544b --- /dev/null +++ b/quick-container/src/main/dockercompose/docker-compose.yaml @@ -0,0 +1,30 @@ +version: "2.0" + +services: + redis: + image: redis:latest + container_name: redis + hostname: redis + ports: + - "6379:6379" + restart: always + + + quick-docker: + image: vector4wang/java:8 + container_name: qd + hostname: qd + depends_on: + - redis + ports: + - "8083:8080" + volumes: + - "/Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/conf:/app/conf" + - "/Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/lib:/app/lib" + - "/Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/bin:/app/bin" + - "/Users/wangxc/Code/Github/spring-boot-quick/quick-container/src/main/dockercompose/entrypoint.sh:/app/scripts/entrypoint.sh" + - "/Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/logs:/app/logs" + environment: + - "JAVA_OPTS=-Xms2g -Xmx4g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -Xss512k -XX:+PrintGCDateStamps" + restart: always + entrypoint: /app/scripts/entrypoint.sh -d redis:6379 -c 'sh /app/bin/start.sh'; \ No newline at end of file diff --git a/quick-container/src/main/dockercompose/entrypoint.sh b/quick-container/src/main/dockercompose/entrypoint.sh new file mode 100755 index 00000000..e054f7e2 --- /dev/null +++ b/quick-container/src/main/dockercompose/entrypoint.sh @@ -0,0 +1,45 @@ +#!/bin/bash +#set -x +#****************************************************************************** +# @file : entrypoint.sh +# @author : network +# @date : 2022年03月01日 +# +# @brief : entry point for manage service start order +# history : init +#****************************************************************************** + +: ${SLEEP_SECOND:=2} + +wait_for() { + echo Waiting for $1 to listen on $2... + while ! nc -z $1 $2; do echo waiting...; sleep $SLEEP_SECOND; done +} + +declare DEPENDS +declare CMD + +while getopts "d:c:" arg +do + case $arg in + d) + DEPENDS=$OPTARG + ;; + c) + CMD=$OPTARG + ;; + ?) + echo "unkonw argument" + exit 1 + ;; + esac +done + +for var in ${DEPENDS//,/ } +do + host=${var%:*} + port=${var#*:} + wait_for $host $port +done + +eval $CMD \ No newline at end of file diff --git a/quick-container/src/main/java/com/docker/DockerApplication.java b/quick-container/src/main/java/com/docker/DockerApplication.java new file mode 100644 index 00000000..5d608578 --- /dev/null +++ b/quick-container/src/main/java/com/docker/DockerApplication.java @@ -0,0 +1,49 @@ +package com.docker; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.rmi.server.UID; +import java.util.UUID; + +/** + * Created with IDEA + * User: vector + * Data: 2018/1/17 + * Time: 13:49 + * Description: + */ +@SpringBootApplication +@RestController +@Slf4j +public class DockerApplication { + + + @Value("${test.value:}") + private String conVal; + + @Autowired + private RedisTemplate redisTemplate; + + @RequestMapping("/hello") + public String hello() { + log.info("接收到请求》》》》》》》》》》》》》》》》》》》》》》》"+UUID.randomUUID().toString()); + Object docker = "redis server error"; + try { + docker = redisTemplate.opsForValue().getAndSet("docker", UUID.randomUUID().toString()); + } catch (Exception e) { + log.error("redis",e); + } + return "

Hello Spring-Boot Maven K3S


" + docker +"
" + conVal; + } + + public static void main(String[] args) { + SpringApplication.run(DockerApplication.class); + } +} diff --git a/quick-container/src/main/java/com/docker/config/RedisConfig.java b/quick-container/src/main/java/com/docker/config/RedisConfig.java new file mode 100644 index 00000000..64283d7f --- /dev/null +++ b/quick-container/src/main/java/com/docker/config/RedisConfig.java @@ -0,0 +1,48 @@ +package com.docker.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; + +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport { + + @Bean + @Override + public KeyGenerator keyGenerator() { + return (target, method, params) -> { + StringBuilder sb = new StringBuilder(); + sb.append(target.getClass().getName()); + sb.append(method.getName()); + for (Object obj : params) { + sb.append(obj.toString()); + } + return sb.toString(); + }; + } + + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + StringRedisTemplate template = new StringRedisTemplate(factory); + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(om); + template.setValueSerializer(jackson2JsonRedisSerializer); + template.afterPropertiesSet(); + return template; + } + +} \ No newline at end of file diff --git a/quick-container/src/main/k3s/app-yaml/README.md b/quick-container/src/main/k3s/app-yaml/README.md new file mode 100644 index 00000000..e2436760 --- /dev/null +++ b/quick-container/src/main/k3s/app-yaml/README.md @@ -0,0 +1,58 @@ +[kubectl 命令大全](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-em-configmap-em-) + +# 注意:该种方式只适合单节点集群方式部署,因为pod的配置、二进制文件都从主机挂载进去的 + +### sh -c 的重要性 + +命令必须得加 `sh -c` + +### k8s镜像仓库设置 + + + +### 文件挂载 +k3d 本身就是个容器集群,还需要将主机的文件挂进去,然后才能被k3d里面的k3s集群使用 + +### 使用PV时报错 +```bash +# 开启nfs存储,k8s 1.20后如果要使用nfs需要开启 +--kube-apiserver-arg=feature-gates=RemoveSelfLink=false +``` +参考: +- https://k3d.io/v5.2.2/faq/faq/ +- https://k3d.io/v5.2.2/usage/configfile/ + +在 k3d 创建cluster的config中指定k3s的参数 + + +### yaml 示例 +https://www.cnblogs.com/shaozhiqi/p/12393962.html + + +### 过程脚本 +```bash +k3d cluster create --config cluster.yml + +kubectl apply -f app.yaml +``` +--- +2022年04月18日 更新 + +### 使用configmap挂载配置文件 +以目录方式创建configmap +```bash +kubectl create configmap my-config --from-file=path/to/bar +``` + +yml挂载 +```docker +volumes: + - name: conf + configMap: + name: app-conf +``` +可以通过`kubectl edit cm app-conf` 进行实时更新,之后再重启pod即可更新配置文件 + + +--- + diff --git a/quick-container/src/main/k3s/app-yaml/app-cm.yaml b/quick-container/src/main/k3s/app-yaml/app-cm.yaml new file mode 100644 index 00000000..65de4a57 --- /dev/null +++ b/quick-container/src/main/k3s/app-yaml/app-cm.yaml @@ -0,0 +1,100 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: quick-app + name: quick-app +spec: + ports: + - name: tcp-8080 + protocol: TCP + targetPort: 8080 + nodePort: 30901 + port: 8080 + selector: + app: quick-app + sessionAffinity: None + type: NodePort + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: quick-app + name: quick-app +spec: + replicas: 1 + selector: + matchLabels: + app: quick-app + template: + metadata: + labels: + app: quick-app + spec: + containers: + - image: vector4wang/java:8 + imagePullPolicy: IfNotPresent + name: quick-app + command: + - 'sh' + - '-c' + - '/app/bin/start.sh' + ports: + - containerPort: 8080 + name: tcp-8080 + protocol: TCP + volumeMounts: + - mountPath: /app/conf + name: conf + - mountPath: /app/lib + name: lib + - mountPath: /app/bin + name: bin + - mountPath: /app/logs + name: logfile + livenessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: 90 + timeoutSeconds: 2 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: 90 + timeoutSeconds: 2 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + + restartPolicy: Always + terminationGracePeriodSeconds: 30 + volumes: + - name: timefile + hostPath: + path: /etc/localtime + type: File +# - name: conf +# hostPath: +# path: /Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/conf +# type: DirectoryOrCreate + - name: conf + configMap: + name: app-conf + - name: lib + hostPath: + path: /Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/lib + type: DirectoryOrCreate + - name: bin + hostPath: + path: /Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/bin + type: DirectoryOrCreate + - name: logfile + hostPath: + path: /Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/logs + type: DirectoryOrCreate \ No newline at end of file diff --git a/quick-container/src/main/k3s/app-yaml/app.yaml b/quick-container/src/main/k3s/app-yaml/app.yaml new file mode 100644 index 00000000..ce36a94d --- /dev/null +++ b/quick-container/src/main/k3s/app-yaml/app.yaml @@ -0,0 +1,97 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: quick-app + name: quick-app +spec: + ports: + - name: tcp-8080 + protocol: TCP + targetPort: 8080 + nodePort: 30901 + port: 8080 + selector: + app: quick-app + sessionAffinity: None + type: NodePort + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: quick-app + name: quick-app +spec: + replicas: 1 + selector: + matchLabels: + app: quick-app + template: + metadata: + labels: + app: quick-app + spec: + containers: + - image: vector4wang/java:8 + imagePullPolicy: IfNotPresent + name: quick-app + command: + - 'sh' + - '-c' + - '/app/bin/start.sh' + ports: + - containerPort: 8080 + name: tcp-8080 + protocol: TCP + volumeMounts: + - mountPath: /app/conf + name: conf + - mountPath: /app/lib + name: lib + - mountPath: /app/bin + name: bin + - mountPath: /app/logs + name: logfile + livenessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: 90 + timeoutSeconds: 2 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: 90 + timeoutSeconds: 2 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + + restartPolicy: Always + terminationGracePeriodSeconds: 30 + volumes: + - name: timefile + hostPath: + path: /etc/localtime + type: File + - name: conf + hostPath: + path: /Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/conf + type: DirectoryOrCreate + - name: lib + hostPath: + path: /Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/lib + type: DirectoryOrCreate + - name: bin + hostPath: + path: /Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/bin + type: DirectoryOrCreate + - name: logfile + hostPath: + path: /Users/wangxc/Code/Github/spring-boot-quick/quick-container/target/logs + type: DirectoryOrCreate \ No newline at end of file diff --git a/quick-container/src/main/k3s/app-yaml/cluster.yaml b/quick-container/src/main/k3s/app-yaml/cluster.yaml new file mode 100644 index 00000000..f3725122 --- /dev/null +++ b/quick-container/src/main/k3s/app-yaml/cluster.yaml @@ -0,0 +1,36 @@ +apiVersion: k3d.io/v1alpha3 +kind: Simple +name: mycluster +servers: 1 +agents: 2 +kubeAPI: + hostPort: "6443" # same as `--api-port '6443'` +ports: + # The range of valid ports is 30000-32767 + - port: 9001:30901 # same as `--port '8080:80@loadbalancer'` + nodeFilters: + - loadbalancer + - port: 8443:443 # same as `--port '8443:443@loadbalancer'` + nodeFilters: + - loadbalancer + +registries: # define how registries should be created or used + config: | # define contents of the `registries.yaml` file (or reference a file); same as `--registry-config /path/to/config.yaml` + mirrors: + docker.io: + endpoint: + - "https://5d1wgx44.mirror.aliyuncs.com" + + +volumes: # repeatable flags are represented as YAML lists k3d 本身就是个容器集群,还需要将主机的文件挂进去,然后才能被k3d里面的k3s集群使用 + - volume: /Users/wangxc/Code/Github/spring-boot-quick/quick-container:/Users/wangxc/Code/Github/spring-boot-quick/quick-container # same as `--volume '/my/host/path:/path/in/node@server:0;agent:*'` + nodeFilters: + - server:0 + - agent:* + +options: + k3s: + extraArgs: + - arg: --kube-apiserver-arg=feature-gates=RemoveSelfLink=false # 开启nfs存储,k8s 1.20后如果要使用nfs需要开启 + nodeFilters: + - server:* diff --git a/quick-container/src/main/k3s/app-yaml/index.html b/quick-container/src/main/k3s/app-yaml/index.html new file mode 100644 index 00000000..deadbc89 --- /dev/null +++ b/quick-container/src/main/k3s/app-yaml/index.html @@ -0,0 +1 @@ +

hello nginx!

\ No newline at end of file diff --git a/quick-container/src/main/k3s/app-yaml/nginx.yaml b/quick-container/src/main/k3s/app-yaml/nginx.yaml new file mode 100644 index 00000000..4e53949f --- /dev/null +++ b/quick-container/src/main/k3s/app-yaml/nginx.yaml @@ -0,0 +1,53 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: nginx-demo + name: nginx-demo +spec: + ports: + - name: nginx-demo + nodePort: 30901 + port: 80 + protocol: TCP + targetPort: 80 + selector: + app: nginx-demo + type: NodePort + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx-demo + name: nginx-demo +spec: + replicas: 1 + selector: + matchLabels: + app: nginx-demo + template: + metadata: + labels: + app: nginx-demo + spec: + containers: + - image: nginx + imagePullPolicy: IfNotPresent + name: nginx-demo + ports: + - containerPort: 80 + name: tcp-8080 + protocol: TCP + volumeMounts: + - mountPath: /usr/share/nginx/html + name: html + readOnly: true + restartPolicy: Always + volumes: + - name: html + hostPath: + path: /Users/wangxc/Code/Github/spring-boot-quick/quick-container/src/main/k3s + type: DirectoryOrCreate diff --git a/quick-container/src/main/k3s/ingress/cluster.yaml b/quick-container/src/main/k3s/ingress/cluster.yaml new file mode 100644 index 00000000..3508a4cb --- /dev/null +++ b/quick-container/src/main/k3s/ingress/cluster.yaml @@ -0,0 +1,14 @@ +apiVersion: k3d.io/v1alpha2 +kind: Simple +name: mycluster +servers: 1 +agents: 2 +kubeAPI: + hostPort: "6443" # same as `--api-port '6443'` +ports: + - port: 9001:80 # same as `--port '8080:80@loadbalancer'` + nodeFilters: + - loadbalancer + - port: 8443:443 # same as `--port '8443:443@loadbalancer'` + nodeFilters: + - loadbalancer diff --git a/quick-container/src/main/k3s/ingress/nginx-a.yaml b/quick-container/src/main/k3s/ingress/nginx-a.yaml new file mode 100644 index 00000000..99017fd0 --- /dev/null +++ b/quick-container/src/main/k3s/ingress/nginx-a.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: web-a + name: web-a +spec: + replicas: 1 + selector: + matchLabels: + app: web-a + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: web-a + spec: + containers: + - image: nginx + name: nginx + ports: + - containerPort: 80 + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 80m + memory: 128Mi +status: {} diff --git a/quick-container/src/main/k3s/ingress/nginx-b.yaml b/quick-container/src/main/k3s/ingress/nginx-b.yaml new file mode 100644 index 00000000..e591121e --- /dev/null +++ b/quick-container/src/main/k3s/ingress/nginx-b.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: web-b + name: web-b +spec: + replicas: 1 + selector: + matchLabels: + app: web-b + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: web-b + spec: + containers: + - image: nginx + name: nginx + ports: + - containerPort: 80 + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 80m + memory: 128Mi +status: {} diff --git a/quick-container/src/main/k3s/ingress/nginx-ingress-a.yaml b/quick-container/src/main/k3s/ingress/nginx-ingress-a.yaml new file mode 100644 index 00000000..ab173915 --- /dev/null +++ b/quick-container/src/main/k3s/ingress/nginx-ingress-a.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: app-ingress-a +spec: + rules: + - host: localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: app-service-a # 链接的是上面svc的名字 + port: + number: 80 diff --git a/quick-container/src/main/k3s/ingress/nginx-ingress-b.yaml b/quick-container/src/main/k3s/ingress/nginx-ingress-b.yaml new file mode 100644 index 00000000..2658b90c --- /dev/null +++ b/quick-container/src/main/k3s/ingress/nginx-ingress-b.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: nginx-ingress-b +spec: + rules: + - host: localhost.k3d.ingress + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: app-service-b # 链接的是上面svc的名字 + port: + number: 80 diff --git a/quick-container/src/main/k3s/ingress/nginx-service-a.yaml b/quick-container/src/main/k3s/ingress/nginx-service-a.yaml new file mode 100644 index 00000000..08c1742f --- /dev/null +++ b/quick-container/src/main/k3s/ingress/nginx-service-a.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: web-service-a + name: app-service-a +spec: + ports: + - name: + port: 80 + protocol: TCP + targetPort: 80 + selector: + app: web-a diff --git a/quick-container/src/main/k3s/ingress/nginx-service-b.yaml b/quick-container/src/main/k3s/ingress/nginx-service-b.yaml new file mode 100644 index 00000000..6e26848b --- /dev/null +++ b/quick-container/src/main/k3s/ingress/nginx-service-b.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: web-service-b + name: app-service-b +spec: + ports: + - name: + port: 80 + protocol: TCP + targetPort: 80 + selector: + app: web-b diff --git a/quick-container/src/main/k3s/nodeport/cluster.yaml b/quick-container/src/main/k3s/nodeport/cluster.yaml new file mode 100644 index 00000000..5bbb2a3c --- /dev/null +++ b/quick-container/src/main/k3s/nodeport/cluster.yaml @@ -0,0 +1,14 @@ +apiVersion: k3d.io/v1alpha2 +kind: Simple +name: mycluster +servers: 1 +agents: 2 +kubeAPI: + hostPort: "6443" # same as `--api-port '6443'` +ports: + - port: 8087:30080 # same as `--port '8080:80@loadbalancer'` + nodeFilters: + - loadbalancer + - port: 8443:443 # same as `--port '8443:443@loadbalancer'` + nodeFilters: + - loadbalancer diff --git a/quick-container/src/main/k3s/nodeport/nginx-service.yaml b/quick-container/src/main/k3s/nodeport/nginx-service.yaml new file mode 100644 index 00000000..2c12db48 --- /dev/null +++ b/quick-container/src/main/k3s/nodeport/nginx-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: web-service + name: app-service +spec: + ports: + - name: + nodePort: 30080 + port: 80 + protocol: TCP + targetPort: 80 + selector: + app: web + type: NodePort diff --git a/quick-container/src/main/k3s/nodeport/nginx.yaml b/quick-container/src/main/k3s/nodeport/nginx.yaml new file mode 100644 index 00000000..5a0e6b2d --- /dev/null +++ b/quick-container/src/main/k3s/nodeport/nginx.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: web + name: web +spec: + replicas: 1 + selector: + matchLabels: + app: web + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: web + spec: + containers: + - image: nginx + name: nginx + ports: + - containerPort: 80 + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 80m + memory: 128Mi +status: {} diff --git a/quick-container/src/main/resources/application.properties b/quick-container/src/main/resources/application.properties new file mode 100644 index 00000000..8859122b --- /dev/null +++ b/quick-container/src/main/resources/application.properties @@ -0,0 +1,13 @@ +server.port=8080 + +spring.redis.database=1 +spring.redis.host=localhost +spring.redis.port=6379 +spring.redis.password= +spring.redis.pool.max-active=8 +spring.redis.pool.max-wait=-1 +spring.redis.pool.max-idle=8 +spring.redis.pool.min-idle=0 +spring.redis.timeout=500 + +test.value=???? \ No newline at end of file diff --git a/quick-dubbo-nacos/README.md b/quick-dubbo-nacos/README.md new file mode 100644 index 00000000..b6fef613 --- /dev/null +++ b/quick-dubbo-nacos/README.md @@ -0,0 +1,17 @@ +# springboot dubbo nacos整合记录 + +[nacos与dubbo是否重叠](https://www.zhihu.com/question/451932038) + +### 部署nacos服务 +[使用docker快速启动nacos服务](https://hub.docker.com/r/nacos/nacos-server) +``` +docker pull nacos/nacos-server +``` + +```docker +# 参照官方文档,这里使用的 Standalone Mysql +docker-compose -f example/standalone-mysql-5.7.yaml up +``` +启动成功访问http://localhost:8848/nacos/ + +用户名和密码都是nacos \ No newline at end of file diff --git a/quick-dubbo-nacos/consumer/pom.xml b/quick-dubbo-nacos/consumer/pom.xml new file mode 100644 index 00000000..35921cec --- /dev/null +++ b/quick-dubbo-nacos/consumer/pom.xml @@ -0,0 +1,52 @@ + + + + quick-dubbo-nacos + com.quick + 1.0-SNAPSHOT + + 4.0.0 + + consumer + + + 8 + 8 + + + + + com.quick + provider + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud + spring-cloud-starter-dubbo + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + org.apache.commons + commons-lang3 + + + + \ No newline at end of file diff --git a/quick-dubbo-nacos/consumer/src/main/java/com/quick/nacos/consumer/NacosConsumerApp.java b/quick-dubbo-nacos/consumer/src/main/java/com/quick/nacos/consumer/NacosConsumerApp.java new file mode 100644 index 00000000..1e237ff1 --- /dev/null +++ b/quick-dubbo-nacos/consumer/src/main/java/com/quick/nacos/consumer/NacosConsumerApp.java @@ -0,0 +1,19 @@ +package com.quick.nacos.consumer; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +/** + * + * @author wangxc + * @date: 2022/3/5 10:39 PM + * + */ +@SpringBootApplication +@EnableDiscoveryClient +public class NacosConsumerApp { + public static void main(String[] args) { + SpringApplication.run(NacosConsumerApp.class, args); + } +} diff --git a/quick-dubbo-nacos/consumer/src/main/java/com/quick/nacos/consumer/controller/HelloController.java b/quick-dubbo-nacos/consumer/src/main/java/com/quick/nacos/consumer/controller/HelloController.java new file mode 100644 index 00000000..3f961637 --- /dev/null +++ b/quick-dubbo-nacos/consumer/src/main/java/com/quick/nacos/consumer/controller/HelloController.java @@ -0,0 +1,38 @@ +package com.quick.nacos.consumer.controller; + +import com.alibaba.nacos.api.config.annotation.NacosValue; +import com.quick.nacos.provider.HelloProvider; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * + * @author wangxc + * @date: 2022/3/5 10:40 PM + * + */ +@RestController +@RefreshScope//自动刷新配置 +public class HelloController { + + @DubboReference(interfaceClass = HelloProvider.class, + interfaceName = "${service.name}", + version = "${service.version}") + private HelloProvider helloProvider; + /** + * nacos 配置的json格式 {"user":"whhhh"} + */ + @Value(value = "${user}") + private String helloSuffix; + + @GetMapping("hello/{param}") + public String hello(@PathVariable("param") String param) { + return helloProvider.hello(param + helloSuffix); + } +} diff --git a/quick-dubbo-nacos/consumer/src/main/resources/application.properties b/quick-dubbo-nacos/consumer/src/main/resources/application.properties new file mode 100644 index 00000000..1417584a --- /dev/null +++ b/quick-dubbo-nacos/consumer/src/main/resources/application.properties @@ -0,0 +1,21 @@ +server.port=8081 + +spring.application.name=nacos-consumer + +spring.cloud.nacos.discovery.server-addr=localhost:8848 +spring.cloud.nacos.config.refresh-enabled=true +spring.cloud.nacos.config.group=${spring.application.name} +#nacos.discovery.server-addr=localhost:8848 +spring.main.allow-bean-definition-overriding=true + +dubbo.registry.address=spring-cloud://localhost:8848 +dubbo.protocol.name=dubbo +dubbo.protocol.port=-1 + +dubbo.consumer.check=false + +dubbo.cloud.subscribed-services=nacos-provider + + +service.version=1.0.0 +service.name=helloService \ No newline at end of file diff --git a/quick-dubbo-nacos/pom.xml b/quick-dubbo-nacos/pom.xml new file mode 100644 index 00000000..1c681cf8 --- /dev/null +++ b/quick-dubbo-nacos/pom.xml @@ -0,0 +1,27 @@ + + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + 4.0.0 + quick-dubbo-nacos + pom + + provider + consumer + service + share-config + + + + 8 + 8 + + + \ No newline at end of file diff --git a/quick-dubbo-nacos/provider/pom.xml b/quick-dubbo-nacos/provider/pom.xml new file mode 100644 index 00000000..91c09378 --- /dev/null +++ b/quick-dubbo-nacos/provider/pom.xml @@ -0,0 +1,19 @@ + + + + quick-dubbo-nacos + com.quick + 1.0-SNAPSHOT + + 4.0.0 + + provider + + + 8 + 8 + + + \ No newline at end of file diff --git a/quick-dubbo-nacos/provider/src/main/java/com.quick.nacos.provider/HelloProvider.java b/quick-dubbo-nacos/provider/src/main/java/com.quick.nacos.provider/HelloProvider.java new file mode 100644 index 00000000..d39b377f --- /dev/null +++ b/quick-dubbo-nacos/provider/src/main/java/com.quick.nacos.provider/HelloProvider.java @@ -0,0 +1,12 @@ +package com.quick.nacos.provider; + +/** + * + * @author wangxc + * @date: 2022/3/5 10:17 PM + * + */ +public interface HelloProvider { + + String hello(String param); +} diff --git a/quick-dubbo-nacos/provider/src/main/java/com.quick.nacos.provider/OtherProvider.java b/quick-dubbo-nacos/provider/src/main/java/com.quick.nacos.provider/OtherProvider.java new file mode 100644 index 00000000..48145cd0 --- /dev/null +++ b/quick-dubbo-nacos/provider/src/main/java/com.quick.nacos.provider/OtherProvider.java @@ -0,0 +1,11 @@ +package com.quick.nacos.provider; + +/** + * + * @author wangxc + * @date: 2022/3/6 10:49 AM + * + */ +public interface OtherProvider { + String otherHello(String param); +} diff --git a/quick-dubbo-nacos/service/pom.xml b/quick-dubbo-nacos/service/pom.xml new file mode 100644 index 00000000..3eac12b4 --- /dev/null +++ b/quick-dubbo-nacos/service/pom.xml @@ -0,0 +1,50 @@ + + + + quick-dubbo-nacos + com.quick + 1.0-SNAPSHOT + + 4.0.0 + + service + + + 8 + 8 + + provider + + + + com.quick + provider + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud + spring-cloud-starter-dubbo + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + org.apache.commons + commons-lang3 + + + + + \ No newline at end of file diff --git a/quick-dubbo-nacos/service/src/main/java/.DS_Store b/quick-dubbo-nacos/service/src/main/java/.DS_Store new file mode 100644 index 00000000..0a8bc21e Binary files /dev/null and b/quick-dubbo-nacos/service/src/main/java/.DS_Store differ diff --git a/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/NacosServiceApp.java b/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/NacosServiceApp.java new file mode 100644 index 00000000..ca39afb3 --- /dev/null +++ b/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/NacosServiceApp.java @@ -0,0 +1,19 @@ +package com.quick.nacos; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +/** + * + * @author wangxc + * @date: 2022/3/5 10:28 PM + * + */ +@SpringBootApplication +@EnableDiscoveryClient +public class NacosServiceApp { + public static void main(String[] args) { + SpringApplication.run(NacosServiceApp.class, args); + } +} diff --git a/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/config/NacosConfig.java b/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/config/NacosConfig.java new file mode 100644 index 00000000..1716de38 --- /dev/null +++ b/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/config/NacosConfig.java @@ -0,0 +1,12 @@ +package com.quick.nacos.config; + +/** + * + * @author wangxc + * @date: 2022/3/21 6:14 PM + * + */ +public class NacosConfig { + + +} diff --git a/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/service/HelloServiceImpl.java b/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/service/HelloServiceImpl.java new file mode 100644 index 00000000..f0dd3068 --- /dev/null +++ b/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/service/HelloServiceImpl.java @@ -0,0 +1,26 @@ +package com.quick.nacos.service; + +import com.quick.nacos.provider.HelloProvider; +import org.apache.dubbo.config.annotation.DubboService; +import org.apache.dubbo.rpc.RpcContext; +import org.springframework.beans.factory.annotation.Value; + +/** + * + * @author wangxc + * @date: 2022/3/5 10:18 PM + * + */ +@DubboService(version = "${service.version}") +public class HelloServiceImpl implements HelloProvider { + + @Value("${service.name}") + private String serviceName; + + @Override + public String hello(String param) { + RpcContext rpcContext = RpcContext.getContext(); + return String.format("Service [name :%s , port : %d] %s(\"%s\") : Hello,%s", serviceName, + rpcContext.getLocalPort(), rpcContext.getMethodName(), serviceName, param); + } +} diff --git a/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/service/OtherService.java b/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/service/OtherService.java new file mode 100644 index 00000000..f64c7781 --- /dev/null +++ b/quick-dubbo-nacos/service/src/main/java/com/quick/nacos/service/OtherService.java @@ -0,0 +1,18 @@ +package com.quick.nacos.service; + +import com.quick.nacos.provider.OtherProvider; +import org.apache.dubbo.config.annotation.DubboService; + +/** + * + * @author wangxc + * @date: 2022/3/6 10:49 AM + * + */ +@DubboService(version = "${service.version}") +public class OtherService implements OtherProvider { + @Override + public String otherHello(String param) { + return "other hello " + param; + } +} diff --git a/quick-dubbo-nacos/service/src/main/resources/application.properties b/quick-dubbo-nacos/service/src/main/resources/application.properties new file mode 100644 index 00000000..1296b215 --- /dev/null +++ b/quick-dubbo-nacos/service/src/main/resources/application.properties @@ -0,0 +1,22 @@ + +spring.application.name=nacos-provider + +spring.cloud.nacos.discovery.server-addr=localhost:8848 +#nacos.discovery.server-addr=localhost:8848 +#spring.main.allow-bean-definition-overriding=true + +dubbo.scan.base-packages=com.quick.nacos +dubbo.registry.address=spring-cloud://localhost:8848 +dubbo.protocol.id=dubbo +dubbo.registry.id=nacos +dubbo.protocol.name=dubbo +dubbo.protocol.port=-1 +dubbo.consumer.check=false +dubbo.application.id=dubbo-nacos-provider +dubbo.application.name=dubbo-nacos-provider +dubbo.cloud.subscribed-services=nacos-provider + +logging.level.root=info + +service.version=1.0.0 +service.name=helloService \ No newline at end of file diff --git a/quick-dubbo-nacos/share-config/.DS_Store b/quick-dubbo-nacos/share-config/.DS_Store new file mode 100644 index 00000000..f25792a8 Binary files /dev/null and b/quick-dubbo-nacos/share-config/.DS_Store differ diff --git a/quick-dubbo-nacos/share-config/README.md b/quick-dubbo-nacos/share-config/README.md new file mode 100644 index 00000000..a5eec191 --- /dev/null +++ b/quick-dubbo-nacos/share-config/README.md @@ -0,0 +1,8 @@ +# 共享配置 +https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config +https://github.com/alibaba/spring-cloud-alibaba/issues/141 + + +https://www.cnblogs.com/larscheng/p/11416392.html + +https://baijiahao.baidu.com/s?id=1720638939882107917&wfr=spider&for=pc \ No newline at end of file diff --git a/quick-dubbo-nacos/share-config/pom.xml b/quick-dubbo-nacos/share-config/pom.xml new file mode 100644 index 00000000..d6a804d5 --- /dev/null +++ b/quick-dubbo-nacos/share-config/pom.xml @@ -0,0 +1,80 @@ + + + + quick-dubbo-nacos + com.quick + 1.0-SNAPSHOT + + 4.0.0 + + share-config + + + 8 + 8 + + + + + + nacos-common-config + org.example + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud + spring-cloud-starter-dubbo + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + false + + + + make-assembly + package + + single + + + false + + src/main/assembly/assembly.xml + + ${project.artifactId} + + + + + + + + \ No newline at end of file diff --git a/quick-dubbo-nacos/share-config/src/main/assembly/assembly.xml b/quick-dubbo-nacos/share-config/src/main/assembly/assembly.xml new file mode 100644 index 00000000..b33a81f9 --- /dev/null +++ b/quick-dubbo-nacos/share-config/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + assembly-${env.devMode} + + zip + + false + + + src/main/assembly/bin + bin + 0755 + 0755 + + + src/main/assembly/conf + conf + 0744 + 0644 + + + lib + lib + 0744 + 0644 + + + + + lib + + + \ No newline at end of file diff --git a/quick-dubbo-nacos/share-config/src/main/assembly/bin/start.bat b/quick-dubbo-nacos/share-config/src/main/assembly/bin/start.bat new file mode 100644 index 00000000..e69de29b diff --git a/quick-dubbo-nacos/share-config/src/main/assembly/bin/start.sh b/quick-dubbo-nacos/share-config/src/main/assembly/bin/start.sh new file mode 100644 index 00000000..043d67f5 --- /dev/null +++ b/quick-dubbo-nacos/share-config/src/main/assembly/bin/start.sh @@ -0,0 +1,102 @@ +#!/bin/bash +log_file=log."$(date +%Y%m%d%H%M%S)" +exec 1>>$log_file +cd `dirname $0` +BIN_DIR=`pwd` +cd .. +DEPLOY_DIR=`pwd` +CONF_DIR=$DEPLOY_DIR/conf +LOGS_DIR=$DEPLOY_DIR/logs + +## 获取项目名 +SERVER_NAME='test-app' + +#SERVER_NAME=`sed '/disconf.app/!d;s/.*=//' conf/disconf.properties | tr -d '\r'` +#SERVER_NAME=`sed '/dubbo.application.name/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` +#SERVER_PROTOCOL=`sed '/dubbo.protocol.name/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` +#SERVER_PORT=`sed '/dubbo.protocol.port/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` +#LOGS_FILE=`sed '/dubbo.log4j.file/!d;s/.*=//' conf/dubbo.properties | tr -d '\r'` + + +echo "project name:$SERVER_NAME" +#echo "server protocol:$SERVER_PROTOCOL" +#echo "server port:$SERVER_PORT" + +if [ -z "$SERVER_NAME" ]; then + SERVER_NAME=`hostname` +fi + +PIDS=`ps aux | grep java | grep "$CONF_DIR" |awk '{print $2}'` +if [ -n "$PIDS" ]; then + echo "ERROR: The $SERVER_NAME already started!" + echo "PID: $PIDS" + exit 1 +fi + +#if [ -n "$SERVER_PORT" ]; then +# SERVER_PORT_COUNT=`netstat -tln | grep $SERVER_PORT | wc -l` +# if [ $SERVER_PORT_COUNT -gt 0 ]; then +# echo "ERROR: The $SERVER_NAME port $SERVER_PORT already used!" +# exit 1 +# fi +#fi + +#LOGS_DIR="" +#if [ -n "$LOGS_FILE" ]; then +# LOGS_DIR=`dirname $LOGS_FILE` +#else +# LOGS_DIR=$DEPLOY_DIR/logs +#fi + +if [ ! -d $LOGS_DIR ]; then + mkdir $LOGS_DIR +fi +STDOUT_FILE=$LOGS_DIR/stdout.log + +LIB_DIR=$DEPLOY_DIR/lib +LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"` + +#JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true " +JAVA_DEBUG_OPTS="" +if [ "$1" = "debug" ]; then + JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n " +fi +JAVA_JMX_OPTS="" +if [ "$1" = "jmx" ]; then + JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " +fi +JAVA_MEM_OPTS="" +if [ ! -n "$JAVA_OPTS" ]; then + BITS=`java -version 2>&1 | grep -i 64-bit` + if [ -n "$BITS" ]; then + JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 " + else + JAVA_MEM_OPTS=" -server -Xms1g -Xmx1g -XX:PermSize=128m -XX:SurvivorRatio=2 -XX:+UseParallelGC " + fi +fi + +echo -e "Starting the $SERVER_NAME ...\c" +#nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS com.alibaba.dubbo.container.Main > $STDOUT_FILE 2>&1 & +nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS com.quick.share.config.ShareConfigApplication > $STDOUT_FILE 2>&1 & + + +echo "wait the server starting..." + +COUNT=0 +while [ $COUNT -lt 1 ]; do + echo -e ".\c" + sleep 1 + COUNT=`ps aux | grep java | grep "$DEPLOY_DIR" | awk '{print $2}' | wc -l` + if [ $COUNT -gt 0 ]; then + break + fi +done + +echo "OK!" +PIDS=`ps aux | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'` +echo "PID: $PIDS" +echo "STDOUT: $STDOUT_FILE" + + +## 注意,以下命令防止容器退出 +tailf /dev/null \ No newline at end of file diff --git a/quick-dubbo-nacos/share-config/src/main/assembly/conf/application.properties b/quick-dubbo-nacos/share-config/src/main/assembly/conf/application.properties new file mode 100644 index 00000000..e69de29b diff --git a/quick-dubbo-nacos/share-config/src/main/java/com/quick/share/config/ShareConfigApplication.java b/quick-dubbo-nacos/share-config/src/main/java/com/quick/share/config/ShareConfigApplication.java new file mode 100644 index 00000000..7e7b98d5 --- /dev/null +++ b/quick-dubbo-nacos/share-config/src/main/java/com/quick/share/config/ShareConfigApplication.java @@ -0,0 +1,19 @@ +package com.quick.share.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +/** + * + * @author wangxc + * @date: 2022/3/21 10:21 PM + * + */ +@SpringBootApplication +@EnableDiscoveryClient +public class ShareConfigApplication { + public static void main(String[] args) { + SpringApplication.run(ShareConfigApplication.class, args); + } +} diff --git a/quick-dubbo-nacos/share-config/src/main/java/com/quick/share/config/controller/HelloController.java b/quick-dubbo-nacos/share-config/src/main/java/com/quick/share/config/controller/HelloController.java new file mode 100644 index 00000000..6a706126 --- /dev/null +++ b/quick-dubbo-nacos/share-config/src/main/java/com/quick/share/config/controller/HelloController.java @@ -0,0 +1,31 @@ +package com.quick.share.config.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RefreshScope//自动刷新配置 +public class HelloController { + + /** + * nacos 配置的json格式 {"user":"whhhh"} + */ + @Value(value = "${share.remark}") + private String remark; + + @Value(value = "${test.haha}") + private String redishost; +// + @Autowired + private RedisTemplate redisTemplate; + + @GetMapping("hello") + public String hello() { + Long test = redisTemplate.opsForValue().increment("test"); + return "hello" + remark + " redishost: " + redishost + ": " + test; + } +} \ No newline at end of file diff --git a/quick-dubbo-nacos/share-config/src/main/resources/application.yml b/quick-dubbo-nacos/share-config/src/main/resources/application.yml new file mode 100644 index 00000000..e69de29b diff --git a/quick-dubbo-nacos/share-config/src/main/resources/bootstrap.yaml.temp b/quick-dubbo-nacos/share-config/src/main/resources/bootstrap.yaml.temp new file mode 100644 index 00000000..f6558fa7 --- /dev/null +++ b/quick-dubbo-nacos/share-config/src/main/resources/bootstrap.yaml.temp @@ -0,0 +1,40 @@ +spring: + application: + name: nacos-config-share + cloud: + nacos: + discovery: + server-addr: ${nacos.address} + config: + server-addr: ${nacos.address} + prefix: ${spring.application.name} + file-extension: yml + shared-configs[0]: + data-id: application-common-redis.yml # 配置文件名-Data Id + refresh: true # 是否动态刷新,默认为false + shared-configs[1]: + data-id: server-common.yml # 配置文件名-Data Id + refresh: true # 是否动态刷新,默认为false +# - +# - +# dataId: server-common.yml +# refresh: true +# shared-configs[1]: +# dataId: application-common-redis.yml +# refresh: true + +# -Dspring.profiles.active=idea +--- +# +spring: + profiles: container +nacos: + address: nacos-standalone-mysql:8848 + +--- +spring: + profiles: idea +nacos: + address: localhost:8848 +# +# diff --git a/quick-simhash/pom.xml b/quick-dubbo/dubbo-api/pom.xml similarity index 59% rename from quick-simhash/pom.xml rename to quick-dubbo/dubbo-api/pom.xml index 0e3edd57..275a6a6a 100644 --- a/quick-simhash/pom.xml +++ b/quick-dubbo/dubbo-api/pom.xml @@ -2,18 +2,10 @@ - quick-simhash + dubbo-api com.quick 1.0-SNAPSHOT 4.0.0 - - - org.wltea.ik-analyzer - ik-analyzer - 3.2.8 - - - \ No newline at end of file diff --git a/quick-dubbo/dubbo-api/src/main/java/com/dubbo/api/HelloService.java b/quick-dubbo/dubbo-api/src/main/java/com/dubbo/api/HelloService.java new file mode 100644 index 00000000..f1a0881c --- /dev/null +++ b/quick-dubbo/dubbo-api/src/main/java/com/dubbo/api/HelloService.java @@ -0,0 +1,5 @@ +package com.dubbo.api; + +public interface HelloService { + String sayHello(String name); +} diff --git a/quick-dubbo/dubbo-consumer/pom.xml b/quick-dubbo/dubbo-consumer/pom.xml new file mode 100644 index 00000000..7897d65f --- /dev/null +++ b/quick-dubbo/dubbo-consumer/pom.xml @@ -0,0 +1,42 @@ + + + dubbo-consumer + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../../quick-platform/pom.xml + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + + + + + com.gitee.reger + spring-boot-starter-dubbo + 1.0.9 + + + dubbo-api + com.quick + 1.0-SNAPSHOT + + + + \ No newline at end of file diff --git a/quick-dubbo/dubbo-consumer/src/main/java/com/DubboConsumerApplication.java b/quick-dubbo/dubbo-consumer/src/main/java/com/DubboConsumerApplication.java new file mode 100644 index 00000000..ad619286 --- /dev/null +++ b/quick-dubbo/dubbo-consumer/src/main/java/com/DubboConsumerApplication.java @@ -0,0 +1,11 @@ +package com; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DubboConsumerApplication { + public static void main(String[] args) { + SpringApplication.run(DubboConsumerApplication.class, args); + } +} diff --git a/quick-dubbo/dubbo-consumer/src/main/java/com/dubbo/consumer/controller/ApiController.java b/quick-dubbo/dubbo-consumer/src/main/java/com/dubbo/consumer/controller/ApiController.java new file mode 100644 index 00000000..aa3375d2 --- /dev/null +++ b/quick-dubbo/dubbo-consumer/src/main/java/com/dubbo/consumer/controller/ApiController.java @@ -0,0 +1,21 @@ +package com.dubbo.consumer.controller; + +import com.dubbo.api.HelloService; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@RestController +public class ApiController { + + @Resource + public HelloService helloService; + + @RequestMapping("/sayHello/{name}") + public String hello(@PathVariable("name") String name) { + return helloService.sayHello(name); + } + +} diff --git a/quick-dubbo/dubbo-consumer/src/main/java/com/dubbo/consumer/service/HelloServiceImpl.java b/quick-dubbo/dubbo-consumer/src/main/java/com/dubbo/consumer/service/HelloServiceImpl.java new file mode 100644 index 00000000..58fd5ac1 --- /dev/null +++ b/quick-dubbo/dubbo-consumer/src/main/java/com/dubbo/consumer/service/HelloServiceImpl.java @@ -0,0 +1,17 @@ +package com.dubbo.consumer.service; + +import com.alibaba.dubbo.config.annotation.Reference; +import com.dubbo.api.HelloService; +import org.springframework.stereotype.Component; + +@Component +public class HelloServiceImpl implements HelloService { + + @Reference + public HelloService helloService; + + @Override + public String sayHello(String name) { + return helloService.sayHello(name); + } +} diff --git a/quick-dubbo/dubbo-consumer/src/main/resources/application.properties b/quick-dubbo/dubbo-consumer/src/main/resources/application.properties new file mode 100644 index 00000000..6530aeda --- /dev/null +++ b/quick-dubbo/dubbo-consumer/src/main/resources/application.properties @@ -0,0 +1,15 @@ +server.port = 8082 + +spring.dubbo.application.name=service-consumer + +# dubbo服务发布者实现类注解@service所在的包 +spring.dubbo.base-package=com.dubbo.consumer +spring.dubbo.registry.address=zookeeper://127.0.0.1 +spring.dubbo.registry.port=2181 + +spring.dubbo.protocol.name=dubbo +spring.dubbo.protocol.serialization=hessian2 + +spring.dubbo.consumer.retries=0 +spring.dubbo.consumer.timeout=1000 +spring.dubbo.consumer.check=false diff --git a/quick-dubbo/dubbo-producer/pom.xml b/quick-dubbo/dubbo-producer/pom.xml new file mode 100644 index 00000000..b26be13a --- /dev/null +++ b/quick-dubbo/dubbo-producer/pom.xml @@ -0,0 +1,57 @@ + + + dubbo-producer + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../../quick-platform/pom.xml + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + + + + + com.gitee.reger + spring-boot-starter-dubbo + 1.0.9 + + + dubbo-api + com.quick + 1.0-SNAPSHOT + + + + + + sonatype-nexus-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + \ No newline at end of file diff --git a/quick-dubbo/dubbo-producer/src/main/java/com/DubboProviderApplication.java b/quick-dubbo/dubbo-producer/src/main/java/com/DubboProviderApplication.java new file mode 100644 index 00000000..414de815 --- /dev/null +++ b/quick-dubbo/dubbo-producer/src/main/java/com/DubboProviderApplication.java @@ -0,0 +1,11 @@ +package com; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DubboProviderApplication { + public static void main(String[] args) { + SpringApplication.run(DubboProviderApplication.class, args); + } +} diff --git a/quick-dubbo/dubbo-producer/src/main/java/com/dubbo/producer/service/HelloServiceImpl.java b/quick-dubbo/dubbo-producer/src/main/java/com/dubbo/producer/service/HelloServiceImpl.java new file mode 100644 index 00000000..af27f4d4 --- /dev/null +++ b/quick-dubbo/dubbo-producer/src/main/java/com/dubbo/producer/service/HelloServiceImpl.java @@ -0,0 +1,14 @@ +package com.dubbo.producer.service; + +import com.alibaba.dubbo.config.annotation.Service; +import com.dubbo.api.HelloService; + +@Service +public class HelloServiceImpl implements HelloService { + + @Override + public String sayHello(String name) { + return "Hello " + name; + } + +} diff --git a/quick-dubbo/dubbo-producer/src/main/resources/application.properties b/quick-dubbo/dubbo-producer/src/main/resources/application.properties new file mode 100644 index 00000000..0ea473e2 --- /dev/null +++ b/quick-dubbo/dubbo-producer/src/main/resources/application.properties @@ -0,0 +1,13 @@ +server.port = 8081 + +spring.dubbo.application.name=service-provider + +# dubbo服务发布者实现类注解@service所在的包 +spring.dubbo.base-package=com.dubbo.producer +spring.dubbo.registry.address=zookeeper://127.0.0.1 +spring.dubbo.registry.port=2181 + +spring.dubbo.protocol.name=dubbo +spring.dubbo.protocol.serialization=hessian2 + +spring.dubbo.provider.retries=0 diff --git a/quick-dubbo/pom.xml b/quick-dubbo/pom.xml new file mode 100644 index 00000000..dd477ef6 --- /dev/null +++ b/quick-dubbo/pom.xml @@ -0,0 +1,21 @@ + + + quick-dubbo + + dubbo-producer + dubbo-consumer + dubbo-api + + com.quick + 1.0-SNAPSHOT + 4.0.0 + pom + + 注意三个模块的写法,api来提供统一的接口,provider实现并作为提供者,consumer使用提供的服务, + 这里引入了dubbo-admin管理界面,可自行去官网下载https://github.com/apache/incubator-dubbo/tree/2.5.x/dubbo-admin + + + + \ No newline at end of file diff --git a/quick-dynamic-bean/pom.xml b/quick-dynamic-bean/pom.xml new file mode 100644 index 00000000..abc32e85 --- /dev/null +++ b/quick-dynamic-bean/pom.xml @@ -0,0 +1,49 @@ + + + quick-dynamic-bean + 1.0-SNAPSHOT + 4.0.0 + + 主要目的:根据不同的条件动态加载不同bean[https://blog.csdn.net/tianyaleixiaowu/article/details/78201587] + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + org.springframework.boot + spring-boot-starter-activemq + + + + org.projectlombok + lombok + provided + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/quick-dynamic-bean/src/main/java/com/dynamic/bean/Application.java b/quick-dynamic-bean/src/main/java/com/dynamic/bean/Application.java new file mode 100644 index 00000000..6b8753cb --- /dev/null +++ b/quick-dynamic-bean/src/main/java/com/dynamic/bean/Application.java @@ -0,0 +1,25 @@ +package com.dynamic.bean; + +import com.dynamic.bean.config.DynamicAttrConfig; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.*; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; + +import java.util.Arrays; + +/** + * @Author: wangxc + * @GitHub: https://github.com/vector4wang + * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents + * @BLOG: http://vector4wang.tk + * @wxid: BMHJQS + */ +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class); + } +} diff --git a/quick-dynamic-bean/src/main/java/com/dynamic/bean/box/AnonymousComponent.java b/quick-dynamic-bean/src/main/java/com/dynamic/bean/box/AnonymousComponent.java new file mode 100644 index 00000000..44ed2a4e --- /dev/null +++ b/quick-dynamic-bean/src/main/java/com/dynamic/bean/box/AnonymousComponent.java @@ -0,0 +1,18 @@ +package com.dynamic.bean.box; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.annotation.Description; + +/** + * + * @author wangxc + * @date: 2019/3/13 上午12:04 + * + */ +@Getter +@Setter +@Description(value = "${dynamic.annotate.unpredictable.key}") +public class AnonymousComponent { + private String other; +} diff --git a/quick-dynamic-bean/src/main/java/com/dynamic/bean/box/Person.java b/quick-dynamic-bean/src/main/java/com/dynamic/bean/box/Person.java new file mode 100644 index 00000000..c4c0a9ca --- /dev/null +++ b/quick-dynamic-bean/src/main/java/com/dynamic/bean/box/Person.java @@ -0,0 +1,22 @@ +package com.dynamic.bean.box; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +/** + * + * @author wangxc + * @date: 2019/3/12 下午10:41 + * + */ +@Getter +@Setter +public class Person { + private String id; + private String name; + private String address; + private int age; + private Date birthday; +} diff --git a/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/BeanConfig.java b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/BeanConfig.java new file mode 100644 index 00000000..5d522fac --- /dev/null +++ b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/BeanConfig.java @@ -0,0 +1,86 @@ +package com.dynamic.bean.config; + +import com.dynamic.bean.box.AnonymousComponent; +import com.dynamic.bean.box.Person; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * + * @author wangxc + * @date: 2019/3/13 下午11:38 + * + */ +@Component +@Slf4j +public class BeanConfig { + + private final static String SOURCE_NAME = "SOURCE_NAME"; + + @Value("${dynamic.attr.count}") + private int count; + + @Value("${dynamic.annotate.unpredictable.value}") + private String[] values; + + + @Value("${dynamic.annotate.unpredictable.key}") + private String key; + + @Value("${dynamic.annotate.defaultVal") + private String defaultVal; + + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private AbstractEnvironment environment; + + + @Bean + @ConditionalOnProperty(prefix = "dynamic.", value = "attr.switch") + public void createDynamicAttrConfig() { + log.info("init create dynamic box"); + ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext; + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); + for (int i = 1; i <= count; i++) { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Person.class); + /** + * 设置属性 + */ + beanDefinitionBuilder.addPropertyValue("id", i); + beanDefinitionBuilder.addPropertyValue("name", "wxc"); + beanDefinitionBuilder.addPropertyValue("address", "earth"); + beanDefinitionBuilder.addPropertyValue("age", 1000); + beanDefinitionBuilder.addPropertyValue("birthday", new Date()); + // beanDefinitionBuilder.addDependsOn() 可以添加依赖如注入其他bean等 + /** + * 注册到spring容器中 + */ + beanFactory.registerBeanDefinition("person_" + i, beanDefinitionBuilder.getBeanDefinition()); + } + } + + @Bean + @ConditionalOnProperty(prefix = "dynamic.", value = "annotate.switch") + public void createDynamicAnnotationConfig() { + for (int i = 0; i < values.length; i++) { + DynamicPropertySource dynamicPropertySource = DynamicPropertySource.builder().setSourceName(SOURCE_NAME) + .setProperty(key, values[i]).build(); + environment.getPropertySources().addFirst(dynamicPropertySource); + log.info("{}--->{}", key, environment.getProperty(key)); + } + } +} diff --git a/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/ConditionConfig.java b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/ConditionConfig.java new file mode 100644 index 00000000..79e13e35 --- /dev/null +++ b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/ConditionConfig.java @@ -0,0 +1,86 @@ +package com.dynamic.bean.config; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.condition.*; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import java.util.Arrays; + +/** + * + * @author wangxc + * @date: 2019/3/12 下午11:24 + * + */ +@Component +public class ConditionConfig { + + /** + * 该Abc class位于类路径上时 + */ + @ConditionalOnClass(DynamicAttrConfig.class) + @Bean + public String abc() { + System.err.println("ConditionalOnClass true"); + return ""; + } + + @Bean + public DynamicAttrConfig createAbcBean() { + return new DynamicAttrConfig(); + } + // + + /** + * 存在Abc类的实例时 + */ + @ConditionalOnBean(DynamicAttrConfig.class) + @Bean + public String bean() { + System.err.println("ConditionalOnBean is exist"); + return ""; + } + + @ConditionalOnMissingBean(DynamicAttrConfig.class) + @Bean + public String missBean() { + System.err.println("ConditionalOnBean is missing"); + return ""; + } + + /** + * 表达式为true时 + * spel http://itmyhome.com/spring/expressions.html + */ + @ConditionalOnExpression(value = "2 > 1") + @Bean + public String expresssion() { + System.err.println("expresssion is true"); + return ""; + } + + /** + * 配置文件属性是否为true + */ + @ConditionalOnProperty(value = {"spring.activemq.switch"}, matchIfMissing = false) + @Bean + public String property() { + System.err.println("property is true"); + return ""; + } + + /** + * 打印容器里的所有bean name (box name 为方法名) + * @param appContext + * @return + */ + @Bean + public CommandLineRunner run(ApplicationContext appContext) { + return args -> { + String[] beans = appContext.getBeanDefinitionNames(); + Arrays.stream(beans).sorted().forEach(System.out::println); + }; + } +} diff --git a/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/DynamicAttrConfig.java b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/DynamicAttrConfig.java new file mode 100644 index 00000000..f74fd817 --- /dev/null +++ b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/DynamicAttrConfig.java @@ -0,0 +1,29 @@ +package com.dynamic.bean.config; + +import com.dynamic.bean.box.Person; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.Date; + +/** + * 动态属性 即动态生成的bean只是属性不同,可通过配置文件来设定需要动态启动的个数属性等等,按需配置 + * @Author: wangxc + * @GitHub: https://github.com/vector4wang + * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents + * @BLOG: http://vector4wang.tk + * @wxid: BMHJQS + */ +@Slf4j +@Component +public class DynamicAttrConfig { +} diff --git a/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/DynamicPropertySource.java b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/DynamicPropertySource.java new file mode 100644 index 00000000..f99733cf --- /dev/null +++ b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/DynamicPropertySource.java @@ -0,0 +1,51 @@ +package com.dynamic.bean.config; + +import org.springframework.core.env.MapPropertySource; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author wangxc + * @date: 2019/3/6 下午10:11 + * + */ +public class DynamicPropertySource extends MapPropertySource { + + + public DynamicPropertySource(String name, Map source) { + super(name, source); + } + + public static Builder builder(){ + return new Builder(); + } + + public static class Builder{ + private String sourceName; + private Map sourceMap = new HashMap<>(); + + public Builder setSourceName(String sourceName) { + this.sourceName = sourceName; + return this; + } + + public Builder setProperty(String key, String value) { + this.sourceMap.put(key, value); + return this; + } + + public Builder setProperty(Map sourceMap) { + this.sourceMap.putAll(sourceMap); + return this; + + } + + public DynamicPropertySource build() { + return new DynamicPropertySource(this.sourceName,this.sourceMap); + } + + } + +} diff --git a/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/MyCondition.java b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/MyCondition.java new file mode 100644 index 00000000..74fafa36 --- /dev/null +++ b/quick-dynamic-bean/src/main/java/com/dynamic/bean/config/MyCondition.java @@ -0,0 +1,22 @@ +package com.dynamic.bean.config; + + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * @Author: wangxc + * @GitHub: https://github.com/vector4wang + * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents + * @BLOG: http://vector4wang.tk + * @wxid: BMHJQS + */ +public class MyCondition implements Condition { + + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { + //判断当前系统是Mac,Windows,Linux + return conditionContext.getEnvironment().getProperty("os.name").contains("Windows"); + } +} diff --git a/quick-dynamic-bean/src/main/resources/application.yml b/quick-dynamic-bean/src/main/resources/application.yml new file mode 100644 index 00000000..88aebff9 --- /dev/null +++ b/quick-dynamic-bean/src/main/resources/application.yml @@ -0,0 +1,24 @@ +server: + port: 8080 + +spring: + activemq: + switch: true + broker-url: tcp://60.205.191.82:8081 + user: admin + password: admin + in-memory: true + pool.enabled: false + + +dynamic: + attr: + switch: true + count: 5 + annotate: + switch: true + key: temp + defaultVal: default-person + unpredictable: + key: dynamic.annotate.key + value: one,two,three diff --git a/quick-dynamic-bean/src/test/java/DynamicTest.java b/quick-dynamic-bean/src/test/java/DynamicTest.java new file mode 100644 index 00000000..9ca09d81 --- /dev/null +++ b/quick-dynamic-bean/src/test/java/DynamicTest.java @@ -0,0 +1,56 @@ +import com.dynamic.bean.Application; +import com.dynamic.bean.box.AnonymousComponent; +import com.dynamic.bean.box.Person; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Description; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * + * @author wangxc + * @date: 2019/3/12 下午11:00 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = Application.class) +public class DynamicTest { + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void testDynamicAttr() { + ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext; + System.out.println("================================================================================"); + Person person_1 = (Person) context.getBean("person_1"); + Person person_2 = (Person) context.getBean("person_2"); + Person person_3 = (Person) context.getBean("person_3"); + Person person_4 = (Person) context.getBean("person_4"); + Person person_5 = (Person) context.getBean("person_5"); + System.out.println("person_1===> "+ person_1); + System.out.println("person_2===> "+ person_2); + System.out.println("person_3===> "+ person_3); + System.out.println("person_4===> "+ person_4); + System.out.println("person_5===> "+ person_5); + /** + * person_1===> com.dynamic.bean.box.Person@41fed14f + * person_2===> com.dynamic.bean.box.Person@4d6ee47 + * person_2===> com.dynamic.bean.box.Person@4d6ee47 + * person_3===> com.dynamic.bean.box.Person@a33b4e3 + * person_4===> com.dynamic.bean.box.Person@c6da8bb + * person_5===> com.dynamic.bean.box.Person@3bae64d0 + */ + + } + + @Test + public void testDynamicAnnotation() { + + } +} diff --git a/quick-exception/pom.xml b/quick-exception/pom.xml index 68390f24..e2f9d47c 100644 --- a/quick-exception/pom.xml +++ b/quick-exception/pom.xml @@ -3,19 +3,15 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> quick-exception - com.quick 1.0-SNAPSHOT 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml - - 2.2.2 - org.springframework.boot @@ -29,7 +25,7 @@ org.springframework.boot - spring-boot-starter-log4j + spring-boot-starter-log4j2 org.springframework.boot diff --git a/quick-exception/src/main/java/com/quick/exception/Application.java b/quick-exception/src/main/java/com/quick/exception/ExceptionApplication.java similarity index 80% rename from quick-exception/src/main/java/com/quick/exception/Application.java rename to quick-exception/src/main/java/com/quick/exception/ExceptionApplication.java index 66bf8f04..e7245c8d 100644 --- a/quick-exception/src/main/java/com/quick/exception/Application.java +++ b/quick-exception/src/main/java/com/quick/exception/ExceptionApplication.java @@ -11,8 +11,8 @@ * @wxid: BMHJQS */ @SpringBootApplication -public class Application { +public class ExceptionApplication { public static void main(String[] args) { - SpringApplication.run(Application.class,args); + SpringApplication.run(ExceptionApplication.class,args); } } diff --git a/quick-exception/src/main/java/com/quick/exception/config/AppErrorUrlController.java b/quick-exception/src/main/java/com/quick/exception/config/AppErrorUrlController.java new file mode 100644 index 00000000..319d8fc5 --- /dev/null +++ b/quick-exception/src/main/java/com/quick/exception/config/AppErrorUrlController.java @@ -0,0 +1,30 @@ +package com.quick.exception.config; + +import com.quick.exception.utils.BaseResp; +import com.quick.exception.utils.ResultStatus; +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Created with IDEA + * User: vector + * Data: 2017/12/1 + * Time: 13:42 + * Description: 当用户输入不合法的url时,返回标准结果(json字符串,而不是页面) + */ +@RestController +public class AppErrorUrlController implements ErrorController { + + private static final String PATH = "/error"; + + @RequestMapping(value = PATH) + public BaseResp error() { + return new BaseResp(ResultStatus.error_invalid_argument, ResultStatus.error_invalid_argument.name()); + } + +// @Override +// public String getErrorPath() { +// return PATH; +// } +} diff --git a/quick-exception/src/main/resources/application.properties b/quick-exception/src/main/resources/application.properties index ac304a6f..2c11d0ba 100644 --- a/quick-exception/src/main/resources/application.properties +++ b/quick-exception/src/main/resources/application.properties @@ -1,4 +1,2 @@ -## ö˿ں -server.port=9090 -logging.level.root=INFO \ No newline at end of file +server.port=9090 diff --git a/quick-exception/src/main/resources/log4j2.xml b/quick-exception/src/main/resources/log4j2.xml new file mode 100644 index 00000000..c56d9037 --- /dev/null +++ b/quick-exception/src/main/resources/log4j2.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quick-feign/README.md b/quick-feign/README.md new file mode 100644 index 00000000..0a03a7cb --- /dev/null +++ b/quick-feign/README.md @@ -0,0 +1,8 @@ +# 使用Feign作为http请求客户端 + +使用feign完成调用api的基本功能 +http client 可以根据自己喜好更换httpclietn、okhttp... +添加hystrix + + +后续会了解Feign和hystrix高级功能 diff --git a/quick-feign/pom.xml b/quick-feign/pom.xml new file mode 100644 index 00000000..8d7a1fbd --- /dev/null +++ b/quick-feign/pom.xml @@ -0,0 +1,88 @@ + + + quick-feign + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.alibaba + fastjson + + + + com.netflix.feign + feign-core + + + com.netflix.feign + feign-jackson + + + com.squareup.okhttp3 + mockwebserver + + + + + com.netflix.feign + feign-okhttp + + + + com.squareup.okhttp3 + okhttp + + + + com.netflix.feign + feign-hystrix + + + + com.netflix.archaius + archaius-core + + + + com.netflix.hystrix + hystrix-core + + + + com.netflix.feign + feign-slf4j + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file diff --git a/quick-feign/src/main/java/com/quick/feign/FeignApplication.java b/quick-feign/src/main/java/com/quick/feign/FeignApplication.java new file mode 100644 index 00000000..66b4e409 --- /dev/null +++ b/quick-feign/src/main/java/com/quick/feign/FeignApplication.java @@ -0,0 +1,49 @@ +package com.quick.feign; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.quick.feign.entity.BaseResp; +import com.quick.feign.entity.DomainDetail; +import com.quick.feign.service.FeignService; +import feign.Logger; +import feign.Request; +import feign.Retryer; +import feign.hystrix.HystrixFeign; +import feign.jackson.JacksonDecoder; +import feign.jackson.JacksonEncoder; +import feign.okhttp.OkHttpClient; +import feign.slf4j.Slf4jLogger; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class FeignApplication { + public static void main(String[] args) { + SpringApplication.run(FeignApplication.class, args); + } + + @Bean + FeignService feignTest() { + + + return HystrixFeign.builder() + .client(new OkHttpClient()) + .encoder(new JacksonEncoder()) + .decoder(new JacksonDecoder()) + .logger(new Slf4jLogger(FeignService.class)) + .logLevel(Logger.Level.FULL) + .options(new Request.Options(2000, 3500)) + .retryer(new Retryer.Default(5000, 5000, 3)) + + .target(FeignService.class, "https://www.sojson.com", new FeignService() { + @Override + public BaseResp getDomain(String domain) { + return JSON.parseObject( + "{\"data\":{\"id\":\"11000002000016\",\"sitename\":\"新浪网\",\"sitedomain\":\"sina.com.cn\",\"sitetype\":\"交互式\",\"cdate\":\"2016-01-21\",\"comtype\":\"企业单位\",\"comname\":\"北京新浪互联信息服务有限公司\",\"comaddress\":\"北京市网安总队\",\"updateTime\":\"2017-09-09\"},\"status\":200}", + new TypeReference>(){}); + } + }); + } + +} diff --git a/quick-feign/src/main/java/com/quick/feign/TestMain.java b/quick-feign/src/main/java/com/quick/feign/TestMain.java new file mode 100644 index 00000000..a95ba279 --- /dev/null +++ b/quick-feign/src/main/java/com/quick/feign/TestMain.java @@ -0,0 +1,19 @@ +package com.quick.feign; + +import com.alibaba.fastjson.JSON; +import com.quick.feign.entity.BaseResp; + +/** + * @author 01390942 + * @Description + * @create 2022/1/24 + * @since 1.0.0 + */ +public class TestMain { + public static void main(String[] args) { + BaseResp baseResp = JSON.parseObject( + "{\"data\":{\"id\":\"11000002000016\",\"sitename\":\"新浪网\",\"sitedomain\":\"sina.com.cn\",\"sitetype\":\"交互式\",\"cdate\":\"2016-01-21\",\"comtype\":\"企业单位\",\"comname\":\"北京新浪互联信息服务有限公司\",\"comaddress\":\"北京市网安总队\",\"updateTime\":\"2017-09-09\"},\"status\":200}", + BaseResp.class); + System.out.println(baseResp); + } +} \ No newline at end of file diff --git a/quick-feign/src/main/java/com/quick/feign/controller/Api.java b/quick-feign/src/main/java/com/quick/feign/controller/Api.java new file mode 100644 index 00000000..f9828d35 --- /dev/null +++ b/quick-feign/src/main/java/com/quick/feign/controller/Api.java @@ -0,0 +1,28 @@ +package com.quick.feign.controller; + +import com.quick.feign.entity.BaseResp; +import com.quick.feign.entity.DomainDetail; +import com.quick.feign.service.FeignService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * @author vector + * @Data 2018/9/10 0010 + * @Description + */ +@RestController +public class Api { + + @Resource + private FeignService feignService; + + @GetMapping("/domain/{domain}/detail") + public Object getDomainDetail(@PathVariable("domain") String domain) { + BaseResp domain1 = feignService.getDomain(domain); + return domain1; + } +} diff --git a/quick-feign/src/main/java/com/quick/feign/entity/BaseResp.java b/quick-feign/src/main/java/com/quick/feign/entity/BaseResp.java new file mode 100644 index 00000000..2594e79c --- /dev/null +++ b/quick-feign/src/main/java/com/quick/feign/entity/BaseResp.java @@ -0,0 +1,41 @@ +package com.quick.feign.entity; + +/** + * @author vector + * @Data 2018/9/10 0010 + * @Description TODO + */ +public class BaseResp { + private int status; + private T data; + private String message; + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return "BaseResp{" + "status=" + status + ", data=" + data + ", message='" + message + '\'' + '}'; + } +} diff --git a/quick-feign/src/main/java/com/quick/feign/entity/DomainDetail.java b/quick-feign/src/main/java/com/quick/feign/entity/DomainDetail.java new file mode 100644 index 00000000..0d95e537 --- /dev/null +++ b/quick-feign/src/main/java/com/quick/feign/entity/DomainDetail.java @@ -0,0 +1,111 @@ +package com.quick.feign.entity; + +/** + * @author vector + * @Data 2018/9/10 0010 + * @Description + */ +public class DomainDetail { + + /** + * id : 11000002000016 + * sitename : 新浪网 + * sitedomain : sina.com.cn + * sitetype : 交互式 + * cdate : 2016-01-21 + * comtype : 企业单位 + * comname : 北京新浪互联信息服务有限公司 + * comaddress : 北京市网安总队 + * updateTime : 2017-09-09 + */ + + private String id; + private String sitename; + private String sitedomain; + private String sitetype; + private String cdate; + private String comtype; + private String comname; + private String comaddress; + private String updateTime; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getSitename() { + return sitename; + } + + public void setSitename(String sitename) { + this.sitename = sitename; + } + + public String getSitedomain() { + return sitedomain; + } + + public void setSitedomain(String sitedomain) { + this.sitedomain = sitedomain; + } + + public String getSitetype() { + return sitetype; + } + + public void setSitetype(String sitetype) { + this.sitetype = sitetype; + } + + public String getCdate() { + return cdate; + } + + public void setCdate(String cdate) { + this.cdate = cdate; + } + + public String getComtype() { + return comtype; + } + + public void setComtype(String comtype) { + this.comtype = comtype; + } + + public String getComname() { + return comname; + } + + public void setComname(String comname) { + this.comname = comname; + } + + public String getComaddress() { + return comaddress; + } + + public void setComaddress(String comaddress) { + this.comaddress = comaddress; + } + + public String getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(String updateTime) { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return "DomainDetail{" + "id='" + id + '\'' + ", sitename='" + sitename + '\'' + ", sitedomain='" + sitedomain + + '\'' + ", sitetype='" + sitetype + '\'' + ", cdate='" + cdate + '\'' + ", comtype='" + comtype + '\'' + + ", comname='" + comname + '\'' + ", comaddress='" + comaddress + '\'' + ", updateTime='" + updateTime + + '\'' + '}'; + } +} diff --git a/quick-feign/src/main/java/com/quick/feign/service/FeignService.java b/quick-feign/src/main/java/com/quick/feign/service/FeignService.java new file mode 100644 index 00000000..7e67770e --- /dev/null +++ b/quick-feign/src/main/java/com/quick/feign/service/FeignService.java @@ -0,0 +1,20 @@ +package com.quick.feign.service; + +import com.quick.feign.entity.BaseResp; +import com.quick.feign.entity.DomainDetail; +import feign.Headers; +import feign.Param; +import feign.RequestLine; + +public interface FeignService { + + @RequestLine("GET /api/gongan/{domain}") + @Headers({ + "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + "content-type: application/json;charset=UTF-8"}) + BaseResp getDomain(@Param("domain") String domain); + + + + +} diff --git a/quick-feign/src/main/resources/application.properties b/quick-feign/src/main/resources/application.properties new file mode 100644 index 00000000..ab956e77 --- /dev/null +++ b/quick-feign/src/main/resources/application.properties @@ -0,0 +1,3 @@ +server.port=9090 + +logging.level.root=info \ No newline at end of file diff --git a/quick-feign/src/test/java/ServiceTest/FeignServiceTest.java b/quick-feign/src/test/java/ServiceTest/FeignServiceTest.java new file mode 100644 index 00000000..0bdc91c2 --- /dev/null +++ b/quick-feign/src/test/java/ServiceTest/FeignServiceTest.java @@ -0,0 +1,31 @@ +package ServiceTest; + +import com.quick.feign.FeignApplication; +import com.quick.feign.entity.BaseResp; +import com.quick.feign.entity.DomainDetail; +import com.quick.feign.service.FeignService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; + +@SpringBootTest(classes = FeignApplication.class) +@RunWith(SpringRunner.class) +public class FeignServiceTest { + + @Resource + private FeignService feignService; + + @Test + public void testGetWeather() { + BaseResp weatherResp = feignService.getDomain("sina.com.cn"); + if (weatherResp.getStatus() == 200) { + DomainDetail data = weatherResp.getData(); + System.out.println(data); + }else{ + System.out.println(weatherResp.getMessage()); + } + } +} diff --git a/quick-flowable/57501_178cba2c-31e7-42ee-a5d0-c8ca3e50f762.png b/quick-flowable/57501_178cba2c-31e7-42ee-a5d0-c8ca3e50f762.png new file mode 100644 index 00000000..19ef5b34 Binary files /dev/null and b/quick-flowable/57501_178cba2c-31e7-42ee-a5d0-c8ca3e50f762.png differ diff --git a/quick-flowable/57501_d588f9dc-9b54-44e0-b476-e3b18f62320f_.png b/quick-flowable/57501_d588f9dc-9b54-44e0-b476-e3b18f62320f_.png new file mode 100644 index 00000000..19ef5b34 Binary files /dev/null and b/quick-flowable/57501_d588f9dc-9b54-44e0-b476-e3b18f62320f_.png differ diff --git a/quick-flowable/README.md b/quick-flowable/README.md new file mode 100644 index 00000000..ca6c5717 --- /dev/null +++ b/quick-flowable/README.md @@ -0,0 +1,39 @@ +# Floable 快速入门 + + +### 基础准备 +使用docker部署flowable [all-in-one](https://hub.docker.com/r/flowable/all-in-one) + +```shell +#user: admin +#password: test +docker run -p 8080:8080 -d flowable_aio flowable/all-in-one +``` +- Flowable IDM (http://localhost:8080/flowable-idm) +- Flowable Modeler (http://localhost:8080/flowable-modeler) +- Flowable Task (http://localhost:8080/flowable-task) +- Flowable Admin (http://localhost:8080/flowable-admin) + +### 测试用例 +#### 使用maven引入依赖 +```xml + + org.flowable + flowable-spring-boot-starter + ${flowable.version} + +``` + +#### 初始化表和引擎实例 +```java +ProcessEngineConfiguration cfg=new StandaloneProcessEngineConfiguration().setJdbcUrl( + "jdbc:mysql://127.0.0.1:3306/flowable-sample?characterEncoding=UTF-8").setJdbcUsername("root") + .setJdbcPassword("123456").setJdbcDriver("com.mysql.cj.jdbc.Driver") + .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); +processEngine=cfg.buildProcessEngine(); + +``` + +### 重点 +- 每个节点可以动态设置处理人`flowable:assignee="${manager}"`,后续可通过任务查询,查出某些人是否有任务在处理 **待确认** +- 谁可以处理任务,需由业务系统对接自行控制 **待确认** \ No newline at end of file diff --git a/quick-flowable/pom.xml b/quick-flowable/pom.xml new file mode 100644 index 00000000..2659b408 --- /dev/null +++ b/quick-flowable/pom.xml @@ -0,0 +1,69 @@ + + + quick-flowable + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + 8 + 8 + 6.4.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + + + + + + + + + + org.projectlombok + lombok + true + + + cn.hutool + hutool-all + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.flowable + flowable-spring-boot-starter + ${flowable.version} + + + + mysql + mysql-connector-java + + + + + \ No newline at end of file diff --git a/quick-flowable/src/main/java/com/quick/flowable/FlowableApplication.java b/quick-flowable/src/main/java/com/quick/flowable/FlowableApplication.java new file mode 100644 index 00000000..f19c7f55 --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/FlowableApplication.java @@ -0,0 +1,18 @@ +package com.quick.flowable; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2022/7/21 22:50 + * + */ +@SpringBootApplication +public class FlowableApplication { + public static void main(String[] args) { + SpringApplication.run(FlowableApplication.class, args); + } + +} diff --git a/quick-flowable/src/main/java/com/quick/flowable/common/ResponseCode.java b/quick-flowable/src/main/java/com/quick/flowable/common/ResponseCode.java new file mode 100644 index 00000000..6392d53c --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/common/ResponseCode.java @@ -0,0 +1,50 @@ +package com.quick.flowable.common; + +public enum ResponseCode { + + /* 公共状态码 */ + // 成功 + SUCCESS(200, "成功"), + //失败 + FAILED(400, "失败"), + //未认证(签名错误) + UNAUTHORIZED(401, "签名错误"), + //接口不存在 + NOT_FOUND(404, "此接口不存在"), + //服务器内部错误 + INTERNAL_SERVER_ERROR(500, "系统繁忙,请稍后再试"), + + /* 参数错误:10001-19999 */ + PARAM_IS_INVALID(10001, "参数无效"), + PARAM_IS_BLANK(10002, "参数为空"), + PARAM_TYPE_BIND_ERROR(10003, "参数类型错误"), + PARAM_NOT_COMPLETE(10004, "参数缺失"), + + /* 用户错误:20001-29999*/ + USER_NOT_LOGGED_IN(20001, "用户未登录"), + USER_LOGIN_ERROR(20002, "账号不存在或密码错误"), + USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"), + USER_NOT_EXIST(20004, "用户不存在"), + USER_HAS_EXISTED(20005, "用户已存在"), + LOGIN_CREDENTIAL_EXISTED(20006, "凭证已存在"); + + /* 业务错误:50001-59999 */ + + + private Integer code; + + private String message; + + ResponseCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } +} \ No newline at end of file diff --git a/quick-flowable/src/main/java/com/quick/flowable/common/ResponseData.java b/quick-flowable/src/main/java/com/quick/flowable/common/ResponseData.java new file mode 100644 index 00000000..29f07508 --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/common/ResponseData.java @@ -0,0 +1,100 @@ +package com.quick.flowable.common; + +import java.io.Serializable; + +public class ResponseData implements Serializable { + + /** + * 响应状态码 + */ + private Integer code; + + /** + * 响应信息 + */ + private String message; + + /** + * 响应对象 + */ + private T data; + + private ResponseData() { + } + + private ResponseData(T data) { + this.data = data; + } + + private ResponseData(String message) { + this.message = message; + } + + private ResponseData(Integer code, String message) { + this.code = code; + this.message = message; + } + + private ResponseData(Integer code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public static ResponseData success() { + return new ResponseData(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMessage(), null); + } + + public static ResponseData success(E object) { + return new ResponseData(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMessage(), object); + } + + public static ResponseData success(Integer code, String message, E object) { + return new ResponseData(code, message, object); + } + + public static ResponseData error() { + return new ResponseData(ResponseCode.FAILED.getCode(), ResponseCode.FAILED.getMessage(), null); + } + + public static ResponseData error(ResponseCode code) { + return new ResponseData(code.getCode(), code.getMessage(), null); + } + + public static ResponseData error(String message) { + return new ResponseData(ResponseCode.FAILED.getCode(), message, null); + } + + public static ResponseData error(Integer code, String message) { + return new ResponseData(code, message, null); + } + + public static ResponseData error(Integer code, String message, E object) { + return new ResponseData(code, message, object); + } +} diff --git a/quick-flowable/src/main/java/com/quick/flowable/config/FlowableConfig.java b/quick-flowable/src/main/java/com/quick/flowable/config/FlowableConfig.java new file mode 100644 index 00000000..05de8bd2 --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/config/FlowableConfig.java @@ -0,0 +1,77 @@ +package com.quick.flowable.config; + +import org.flowable.engine.*; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; + +import javax.sql.DataSource; +import java.io.IOException; + +@Configuration +public class FlowableConfig { + @Bean + public void processEngine(DataSourceTransactionManager transactionManager, DataSource dataSource) + throws IOException { + SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration(); + //自动部署已有的流程文件 + Resource[] resources = new PathMatchingResourcePatternResolver().getResources( + ResourceLoader.CLASSPATH_URL_PREFIX + "processes/*.bpmn"); + configuration.setTransactionManager(transactionManager); + // 执行工作流对应的数据源 + configuration.setDataSource(dataSource); + // 是否自动创建流程引擎表 + configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); + configuration.setDeploymentResources(resources); + //configuration.setDbIdentityUsed(false); + //configuration.setAsyncExecutorActivate(false); + // 流程历史等级 + //configuration.setHistoryLevel(HistoryLevel.FULL); + // 流程图字体 + configuration.setActivityFontName("宋体"); + configuration.setAnnotationFontName("宋体"); + configuration.setLabelFontName("宋体"); + //return configuration; + } + + @Bean + public RepositoryService repositoryService(ProcessEngine processEngine) { + return processEngine.getRepositoryService(); + } + + @Bean + public RuntimeService runtimeService(ProcessEngine processEngine) { + return processEngine.getRuntimeService(); + } + + @Bean + public TaskService taskService(ProcessEngine processEngine) { + return processEngine.getTaskService(); + } + + @Bean + public HistoryService historyService(ProcessEngine processEngine) { + return processEngine.getHistoryService(); + } + + @Bean + public ManagementService managementService(ProcessEngine processEngine) { + return processEngine.getManagementService(); + } + + @Bean + public IdentityService identityService(ProcessEngine processEngine) { + return processEngine.getIdentityService(); + } + + @Bean + public FormService formService(ProcessEngine processEngine) { + return processEngine.getFormService(); + } + + +} diff --git a/quick-flowable/src/main/java/com/quick/flowable/controller/FlowableProcessController.java b/quick-flowable/src/main/java/com/quick/flowable/controller/FlowableProcessController.java new file mode 100644 index 00000000..9e8900f3 --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/controller/FlowableProcessController.java @@ -0,0 +1,44 @@ +package com.quick.flowable.controller; + +import cn.hutool.core.bean.BeanUtil; +import com.quick.flowable.common.ResponseData; +import com.quick.flowable.model.DeploymentVO; +import com.quick.flowable.service.handler.ProcessHandler; +import org.flowable.engine.repository.Deployment; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.zip.ZipInputStream; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * 流程模板相关api + * @author wangxc + * @date: 2022/7/25 22:48 + * + */ +@RestController +@RequestMapping("/flowable") +public class FlowableProcessController { + + @Autowired + private ProcessHandler processHandler; + + @RequestMapping("/deployProcess") + public ResponseData deployByZip(String name, String category, String tenantId, MultipartFile file) { + Deployment deployment = null; + try (ZipInputStream zipIn = new ZipInputStream(file.getInputStream(), UTF_8)) { + deployment = processHandler.deploy(name, tenantId, category, zipIn); + } catch (IOException e) { + e.printStackTrace(); + } + //忽略二进制文件(模板文件、模板图片)返回 + DeploymentVO deploymentVO = BeanUtil.copyProperties(deployment, DeploymentVO.class, ""); + return ResponseData.success(deploymentVO); + } + +} diff --git a/quick-flowable/src/main/java/com/quick/flowable/listener/ForgetFightListener.java b/quick-flowable/src/main/java/com/quick/flowable/listener/ForgetFightListener.java new file mode 100644 index 00000000..ec34fcad --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/listener/ForgetFightListener.java @@ -0,0 +1,21 @@ +package com.quick.flowable.listener; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; + +/** + * + * @author wangxc + * @date: 2022/7/23 23:44 + * + */ +@Slf4j +public class ForgetFightListener implements TaskListener { + @Override + public void notify(DelegateTask delegateTask) { + log.info(delegateTask.getOwner()); + log.info(delegateTask.getAssignee()); + log.info(delegateTask.getEventName()); + } +} diff --git a/quick-flowable/src/main/java/com/quick/flowable/listener/SendRejectionMail.java b/quick-flowable/src/main/java/com/quick/flowable/listener/SendRejectionMail.java new file mode 100644 index 00000000..2e51e3d2 --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/listener/SendRejectionMail.java @@ -0,0 +1,21 @@ +package com.quick.flowable.listener; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; + +/** + * + * @author wangxc + * @date: 2022/7/23 22:54 + * + */ +@Slf4j +public class SendRejectionMail implements JavaDelegate { + @Override + public void execute(DelegateExecution execution) { + System.out.println( + "execution.getCurrentFlowElement().getName() = " + execution.getCurrentFlowElement().getName()); + System.out.println("请求被驳回~"); + } +} diff --git a/quick-flowable/src/main/java/com/quick/flowable/model/DeploymentVO.java b/quick-flowable/src/main/java/com/quick/flowable/model/DeploymentVO.java new file mode 100644 index 00000000..33b78cc1 --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/model/DeploymentVO.java @@ -0,0 +1,35 @@ +package com.quick.flowable.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + +/** + * + * @author wangxc + * @date: 2022/7/25 22:56 + * + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DeploymentVO implements Serializable { + private static final long serialVersionUID = 1L; + private String id; + private String name; + private String category; + private String key; + private String tenantId; + private Date deploymentTime; + private String derivedFrom; + private String derivedFromRoot; + private String parentDeploymentId; + private String engineVersion; + private Boolean deleted; + private Boolean inserted; + private String idPrefix; + private Boolean updated; +} diff --git a/quick-flowable/src/main/java/com/quick/flowable/service/ActProcess.java b/quick-flowable/src/main/java/com/quick/flowable/service/ActProcess.java new file mode 100644 index 00000000..da99b639 --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/service/ActProcess.java @@ -0,0 +1,137 @@ +package com.quick.flowable.service; + +import org.flowable.engine.repository.*; +import org.springframework.lang.Nullable; + +import java.io.InputStream; +import java.util.zip.ZipInputStream; + +/** + * 流程定义封装 + */ +public interface ActProcess { + /** + * 创建流程部署对象 + * + * @return + */ + public DeploymentBuilder createDeployment(); + + /** + * 创建流程部署查询对象 + * + * @return + */ + public DeploymentQuery createDeploymentQuery(); + + /** + * 创建流程定义查询对象 + * + * @return + */ + public ProcessDefinitionQuery createProcessDefinitionQuery(); + + /** + * 部署流程定义 + * + * @param url 流程定义文件URL + * @return + */ + Deployment deploy(String url); + + /** + * 部署流程定义---通过inputstream流 + * + * @param name 流程模板文件名字 + * @param tenantId 业务系统标识 + * @param category 流程模板文件类别 + * @param in 流程模板文件流 + * @return + */ + Deployment deploy(String name, @Nullable String tenantId, @Nullable String category, InputStream in); + + /** + * 部署流程定义 + * + * @param url 流程定义文件URL + * @param pngUrl 流程定义文件pngUrl + * @return + */ + Deployment deploy(String url, String pngUrl); + + /** + * 部署压缩包内的流程资源— + * 资源包括:bpmn、png、drl、form等等 + * 流程引擎:内部使用迭代器方式遍历压缩包中文件并读取成响应的文件流 + * + * @param zipInputStream + * @param name 流程模板文件名字 + * @param tenantId 业务系统标识 + * @param category 流程模板文件类别 + * @return + */ + Deployment deploy(String name, String tenantId, String category, ZipInputStream zipInputStream); + + /** + * 部署流程定义 + * + * @param url 流程定义文件URL + * @param drlUrl 规则引擎文件URL + * @return + */ + Deployment deployBpmnAndDrl(String url, String drlUrl); + + /** + * 部署流程定义 + * + * @param url 流程定义文件URL + * @param name 流程定义名称 + * @param category 流程定义类别 + * @return + */ + Deployment deploy(String url, String name, String category); + + /** + * 部署流程定义 + * + * @param url 流程定义文件URL + * @param pngUrl 流程定义文件pngUrl + * @param name 流程定义标识 + * @param category 流程定义类别 + * @return + */ + Deployment deploy(String url, String pngUrl, String name, String category); + + /** + * 根据流程定义key,判断流程定义(模板)是否已经部署过 + * + * @param processDefinitionKey 流程定义key(即:流程模板ID) + * @return + */ + boolean exist(String processDefinitionKey); + + /** + * 根据流程定义key,查询流程定义信息 + * + * @param processDefinitionKey 流程定义key(即:流程模板ID) + * @return + */ + public ProcessDefinition queryByProcessDefinitionKey(String processDefinitionKey); + + /** + * 根据流程部署name,查询流程部署信息(最新) + * + * @param deploymentName 流程部署name + * @return + */ + Deployment deployName(String deploymentName); + + /** + * 给流程定义授权用户 + * + * @param processDefinitionKey 流程定义key + * @param userId 流程定义key + * @throws + */ + void addCandidateStarterUser(String processDefinitionKey, String userId); +} diff --git a/quick-flowable/src/main/java/com/quick/flowable/service/handler/ProcessHandler.java b/quick-flowable/src/main/java/com/quick/flowable/service/handler/ProcessHandler.java new file mode 100644 index 00000000..ad57a65f --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/service/handler/ProcessHandler.java @@ -0,0 +1,118 @@ +package com.quick.flowable.service.handler; + +import cn.hutool.core.lang.Assert; +import com.quick.flowable.service.ActProcess; +import com.quick.flowable.service.impl.ServiceFactory; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.repository.*; +import org.springframework.stereotype.Component; + +import java.io.InputStream; +import java.util.List; +import java.util.zip.ZipInputStream; + +@Component +@Slf4j +public class ProcessHandler extends ServiceFactory implements ActProcess { + + + /** + * 解决问题:https://blog.csdn.net/weixin_43607664/article/details/88664777 + */ + public static final String BPMN_FILE_SUFFIX = ".bpmn"; + + @Override + public DeploymentBuilder createDeployment() { + return repositoryService.createDeployment(); + } + + @Override + public DeploymentQuery createDeploymentQuery() { + return repositoryService.createDeploymentQuery(); + } + + @Override + public ProcessDefinitionQuery createProcessDefinitionQuery() { + return repositoryService.createProcessDefinitionQuery(); + } + + @Override + public Deployment deploy(String bpmnFileUrl) { + Deployment deploy = createDeployment().addClasspathResource(bpmnFileUrl).deploy(); + return deploy; + } + + @Override + public Deployment deploy(String url, String pngUrl) { + + Deployment deploy = createDeployment().addClasspathResource(url).addClasspathResource(pngUrl).deploy(); + return deploy; + } + + @Override + public Deployment deploy(String name, String tenantId, String category, ZipInputStream zipInputStream) { + return createDeployment().addZipInputStream(zipInputStream) + .name(name).category(category).tenantId(tenantId).deploy(); + } + + @Override + public Deployment deployBpmnAndDrl(String url, String drlUrl) { + Deployment deploy = createDeployment().addClasspathResource(url).addClasspathResource(drlUrl).deploy(); + return deploy; + } + + @Override + public Deployment deploy(String url, String name, String category) { + Deployment deploy = createDeployment().addClasspathResource(url).name(name).category(category).deploy(); + return deploy; + } + + @Override + public Deployment deploy(String url, String pngUrl, String name, String category) { + Deployment deploy = createDeployment().addClasspathResource(url).addClasspathResource(pngUrl) + .name(name).category(category).deploy(); + return deploy; + } + + @Override + public boolean exist(String processDefinitionKey) { + ProcessDefinitionQuery processDefinitionQuery + = createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey); + long count = processDefinitionQuery.count(); + return count > 0 ? true : false; + } + + @Override + public Deployment deploy(String name, String tenantId, String category, InputStream in) { + return createDeployment().addInputStream(name + BPMN_FILE_SUFFIX, in) + .name(name) + .tenantId(tenantId) + .category(category) + .deploy(); + + } + + @Override + public ProcessDefinition queryByProcessDefinitionKey(String processDefinitionKey) { + ProcessDefinition processDefinition + = createProcessDefinitionQuery() + .processDefinitionKey(processDefinitionKey) + .active().singleResult(); + return processDefinition; + } + + @Override + public Deployment deployName(String deploymentName) { + List list = repositoryService + .createDeploymentQuery() + .deploymentName(deploymentName).list(); + Assert.notNull(list, "list must not be null"); + return list.get(0); + } + + @Override + public void addCandidateStarterUser(String processDefinitionKey, String userId) { + repositoryService.addCandidateStarterUser(processDefinitionKey, userId); + } + +} diff --git a/quick-flowable/src/main/java/com/quick/flowable/service/impl/ServiceFactory.java b/quick-flowable/src/main/java/com/quick/flowable/service/impl/ServiceFactory.java new file mode 100644 index 00000000..a928bfb6 --- /dev/null +++ b/quick-flowable/src/main/java/com/quick/flowable/service/impl/ServiceFactory.java @@ -0,0 +1,36 @@ +package com.quick.flowable.service.impl; + +import org.flowable.engine.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +public class ServiceFactory { + + @Autowired + protected RepositoryService repositoryService; + + @Autowired + protected RuntimeService runtimeService; + + @Autowired + protected IdentityService identityService; + + @Autowired + protected TaskService taskService; + +// @Autowired +// protected FormService formService; + + @Autowired + protected HistoryService historyService; + + @Autowired + protected ManagementService managementService; + + @Qualifier("processEngine") + @Autowired + protected ProcessEngine processEngine; + +} \ No newline at end of file diff --git a/quick-flowable/src/main/resources/application.properties b/quick-flowable/src/main/resources/application.properties new file mode 100644 index 00000000..3e5b6549 --- /dev/null +++ b/quick-flowable/src/main/resources/application.properties @@ -0,0 +1,12 @@ +server.port=8081 + +spring.main.allow-bean-definition-overriding=true + +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/flowable-sample?characterEncoding=UTF-8 +spring.datasource.username=root +spring.datasource.password=123456 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.servlet.multipart.enabled=true +spring.servlet.multipart.max-file-size=100MB +spring.servlet.multipart.max-request-size=500MB diff --git a/quick-flowable/src/test/java/com/quick/flowable/FlowableTest.java b/quick-flowable/src/test/java/com/quick/flowable/FlowableTest.java new file mode 100644 index 00000000..bef29d03 --- /dev/null +++ b/quick-flowable/src/test/java/com/quick/flowable/FlowableTest.java @@ -0,0 +1,144 @@ +package com.quick.flowable; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.*; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricActivityInstanceQuery; +import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author wangxc + * @date: 2022/7/21 23:03 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = FlowableApplication.class) +@Slf4j +public class FlowableTest { + + static ProcessEngine processEngine = null; + + + /** + * 初始化StandaloneProcessEngineConfiguration + */ + @BeforeClass + public static void initFactory() { + log.info("init ProcessEngineConfiguration"); + ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration().setJdbcUrl( + "jdbc:mysql://127.0.0.1:3306/flowable-sample?characterEncoding=UTF-8").setJdbcUsername("root") + .setJdbcPassword("123456").setJdbcDriver("com.mysql.cj.jdbc.Driver") + // .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); + .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE); + processEngine = cfg.buildProcessEngine(); + + } + // @Test + // public void testInitTable() { + // ProcessEngine processEngine = cfg.buildProcessEngine(); + // } + + @Test + public void testInitProcess() { + Deployment deploy = processEngine.getRepositoryService().createDeployment() + .addClasspathResource("holiday-request.bpmn20.xml") + .name(DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMAT) + "_请假流程").category("holiday") + .deploy(); + System.out.println("deploy.getId() = " + deploy.getId()); + System.out.println("deploy.getName() = " + deploy.getName()); + + } + + @Test + public void testDelProcess() { + processEngine.getRepositoryService().deleteDeployment("25001", true); + } + + @Test + public void testQueryProcess() { + ProcessDefinition processDefinition = processEngine.getRepositoryService().createProcessDefinitionQuery() + .deploymentId("32501").singleResult(); + System.out.println("Found process definition : " + processDefinition.getName()); + System.out.println("processDefinition.getDeploymentId() = " + processDefinition.getDeploymentId()); + System.out.println("processDefinition.getName() = " + processDefinition.getName()); + System.out.println("processDefinition.getKey() = " + processDefinition.getKey()); + System.out.println("processDefinition.getId() = " + processDefinition.getId()); + + } + + + /** + * 初始化一个流程,通过variables完善第一个节点的表单信息 + */ + @Test + public void testStartAProcess() { + RuntimeService runtimeService = processEngine.getRuntimeService(); + HashMap variables = new HashMap<>(); + variables.put("employee", "vector"); + variables.put("nrOfHolidays", "10"); + variables.put("description", "休育儿假"); + + // 设置节点里的变量? + variables.put("manager", "一星"); + ProcessInstance processInstance = runtimeService.startProcessInstanceById("holidayRequest:1:32503", variables); + System.out.println("processInstance.getProcessInstanceId() = " + processInstance.getProcessInstanceId()); + + } + + @Test + public void testCompleteTask() { + TaskService taskService = processEngine.getTaskService(); + Task task = taskService.createTaskQuery().processDefinitionKey("holidayRequest").taskAssignee("一星") + .singleResult(); + System.out.println("task.getDelegationState() = " + task.getDelegationState()); + System.out.println("task.getName() = " + task.getName()); + + + Map variables = new HashMap<>(); + variables.put("approved", false); + taskService.complete(task.getId(), variables); + + } + + + @Test + public void testQueryProcessInstance() { + String processId = "17501"; + RuntimeService runtimeService = processEngine.getRuntimeService(); + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult(); + System.out.println("pi.getProcessInstanceId() = " + pi.getProcessInstanceId()); + } + + + + @Test + public void testQueryHistory() { + HistoryService historyService = processEngine.getHistoryService(); + HistoricActivityInstanceQuery hi = historyService.createHistoricActivityInstanceQuery(); + List list = hi.processDefinitionId("holidayRequest:1:32503").finished() // 历史状态已完成 + .orderByHistoricActivityInstanceEndTime().asc().list(); + for (HistoricActivityInstance history : list) { + System.out.println(history.getActivityName() + ": " + history.getAssignee() + "--" + + history.getActivityId()+": "+ history.getDurationInMillis()); + } + + + } + +} diff --git a/quick-flowable/src/test/java/com/quick/flowable/ForgetFightTest.java b/quick-flowable/src/test/java/com/quick/flowable/ForgetFightTest.java new file mode 100644 index 00000000..e1a53ef3 --- /dev/null +++ b/quick-flowable/src/test/java/com/quick/flowable/ForgetFightTest.java @@ -0,0 +1,201 @@ +package com.quick.flowable; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.*; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricActivityInstanceQuery; +import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.Execution; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.image.ProcessDiagramGenerator; +import org.flowable.task.api.Task; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +/** + * + * @author wangxc + * @date: 2022/7/21 23:03 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = FlowableApplication.class) +@Slf4j +public class ForgetFightTest { + + static ProcessEngine processEngine = null; + + + /** + * 初始化StandalonePffflow.bpmn20.xmlrocessEngineConfiguration + */ + @BeforeClass + public static void initFactory() { + log.info("init ProcessEngineConfiguration"); + ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration().setJdbcUrl( + "jdbc:mysql://127.0.0.1:3306/flowable-sample?characterEncoding=UTF-8").setJdbcUsername("root") + .setJdbcPassword("123456").setJdbcDriver("com.mysql.cj.jdbc.Driver") + // .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); + .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE); + processEngine = cfg.buildProcessEngine(); + + } + + @Test + public void testInitProcess() { + Deployment deploy = processEngine.getRepositoryService().createDeployment() + .addClasspathResource("ffflow.bpmn20.xml") + .name(DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMAT) + "_打卡流程").category("fight") + .deploy(); + System.out.println("deploy.getId() = " + deploy.getId()); + System.out.println("deploy.getName() = " + deploy.getName()); + System.out.println("deploy.getKey() = " + deploy.getKey()); + + } + + @Test + public void testDelProcess() { + processEngine.getRepositoryService().deleteDeployment("45001", true); + } + + @Test + public void testQueryProcess() { + ProcessDefinition processDefinition = processEngine.getRepositoryService().createProcessDefinitionQuery() + .deploymentId("45001").singleResult(); + System.out.println("Found process definition : " + processDefinition.getName()); + System.out.println("processDefinition.getDeploymentId() = " + processDefinition.getDeploymentId()); + System.out.println("processDefinition.getName() = " + processDefinition.getName()); + System.out.println("processDefinition.getKey() = " + processDefinition.getKey()); + System.out.println("processDefinition.getId() = " + processDefinition.getId()); + + } + + + /** + * 初始化一个流程,通过variables完善第一个节点的表单信息 + */ + @Test + public void testStartAProcess() { + RuntimeService runtimeService = processEngine.getRuntimeService(); + HashMap variables = new HashMap<>(); + variables.put("employee", "vector"); + variables.put("datetime", "2022-07-24"); + variables.put("description", "忘记打卡"); + ProcessInstance processInstance = runtimeService.startProcessInstanceById("test-key:1:45004", variables); + System.out.println("processInstance.getProcessInstanceId() = " + processInstance.getProcessInstanceId()); + /** + * 自己提交的时候,应该自带一个处理过程 + */ + } + + @Test + public void testQueryWaitProcessTask() { + TaskService taskService = processEngine.getTaskService(); + Task task = taskService.createTaskQuery().processDefinitionKey("test-key") +// .taskAssignee("$INITIATOR") + .taskAssignee("bm") + .singleResult(); + if (Objects.nonNull(task)) { + System.out.println("task.getDelegationState() = " + task.getDelegationState()); + System.out.println("task.getName() = " + task.getName()); + System.out.println("task.getProcessInstanceId() = " + task.getProcessInstanceId()); + } + + + } + + @Test + public void testProcessNode() { + TaskService taskService = processEngine.getTaskService(); + Task task = taskService.createTaskQuery().processDefinitionKey("test-key").taskAssignee("$INITIATOR") + .singleResult(); + if (Objects.nonNull(task)) { + System.out.println("task.getDelegationState() = " + task.getDelegationState()); + System.out.println("task.getName() = " + task.getName()); + + // 个人审批 + Map variables = new HashMap<>(); + variables.put("manager", "bm"); + taskService.complete(task.getId(), variables); + // 领导审批 + // taskService.complete(task.getId()); + } + + + } + + + @Test + public void testQueryProcessInstance() { + String processId = "17501"; + RuntimeService runtimeService = processEngine.getRuntimeService(); + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult(); + System.out.println("pi.getProcessInstanceId() = " + pi.getProcessInstanceId()); + } + + + @Test + public void testQueryHistory() { + HistoryService historyService = processEngine.getHistoryService(); + HistoricActivityInstanceQuery hi = historyService.createHistoricActivityInstanceQuery(); + List list = hi.processDefinitionId("holidayRequest:1:32503").finished() // 历史状态已完成 + .orderByHistoricActivityInstanceEndTime().asc().list(); + for (HistoricActivityInstance history : list) { + System.out.println( + history.getActivityName() + ": " + history.getAssignee() + "--" + history.getActivityId() + ": " + + history.getDurationInMillis()); + } + + + } + + + @Test + public void testGetImg() throws IOException { + String processId = "57501"; + ProcessInstance pi = processEngine.getRuntimeService().createProcessInstanceQuery().processInstanceId(processId) + .singleResult(); + //流程走完的不显示图 + if (pi == null) { + return; + } + Task task = processEngine.getTaskService().createTaskQuery().processInstanceId(pi.getId()).singleResult(); + //使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象 + String InstanceId = task.getProcessInstanceId(); + List executions = processEngine.getRuntimeService().createExecutionQuery() + .processInstanceId(InstanceId).list(); + //得到正在执行的Activity的Id + List activityIds = new ArrayList<>(); + List flows = new ArrayList<>(); + for (Execution exe : executions) { + List ids = processEngine.getRuntimeService().getActiveActivityIds(exe.getId()); + activityIds.addAll(ids); + } + //获取流程图 + String suffix = "png"; + BpmnModel bpmnModel = processEngine.getRepositoryService().getBpmnModel(pi.getProcessDefinitionId()); + ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration(); + ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator(); + InputStream inputStream = diagramGenerator.generateDiagram(bpmnModel, suffix, activityIds, flows, + engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), + engconf.getClassLoader(), 1.0, true); + + + byte[] bytes = IOUtils.toByteArray(inputStream); + IOUtils.write(bytes,new FileOutputStream(processId + "_" + UUID.randomUUID() + "." + suffix) ); + } +} \ No newline at end of file diff --git a/quick-flowable/src/test/resources/ffflow.bpmn20.xml b/quick-flowable/src/test/resources/ffflow.bpmn20.xml new file mode 100644 index 00000000..1bb254dd --- /dev/null +++ b/quick-flowable/src/test/resources/ffflow.bpmn20.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quick-flowable/src/test/resources/holiday-request.bpmn20.xml b/quick-flowable/src/test/resources/holiday-request.bpmn20.xml new file mode 100644 index 00000000..ee523df2 --- /dev/null +++ b/quick-flowable/src/test/resources/holiday-request.bpmn20.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quick-framework/base-adapter/pom.xml b/quick-framework/base-adapter/pom.xml new file mode 100644 index 00000000..5a9aab1b --- /dev/null +++ b/quick-framework/base-adapter/pom.xml @@ -0,0 +1,48 @@ + + + + quick-framework + com.quick + 1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + base-adapter + 1.0-SNAPSHOT + + + 8 + 8 + + + + + org.apache.shiro + shiro-cas + provided + + + org.apache.shiro + shiro-core + provided + + + org.apache.shiro + shiro-spring + provided + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-test-autoconfigure + + + + \ No newline at end of file diff --git a/quick-framework/base-adapter/src/main/java/com/base/adapter/ShiroConfig.java b/quick-framework/base-adapter/src/main/java/com/base/adapter/ShiroConfig.java new file mode 100644 index 00000000..76b107de --- /dev/null +++ b/quick-framework/base-adapter/src/main/java/com/base/adapter/ShiroConfig.java @@ -0,0 +1,125 @@ +package com.base.adapter; + + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.session.mgt.SessionManager; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.servlet.SimpleCookie; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author 01390942 + * @Description + * @create 2022/1/25 + * @since 1.0.0 + */ +public class ShiroConfig { + + @Bean + @ConditionalOnMissingBean + public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { + DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); + defaultAAP.setProxyTargetClass(true); + return defaultAAP; + } + + @Bean + public SecurityManager securityManager(SessionManager sessionManager) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(new AuthorizingRealm() { + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { + return new SimpleAuthorizationInfo(); + } + + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) + throws AuthenticationException { + return new SimpleAuthenticationInfo(); + } + }); + securityManager.setSessionManager(sessionManager); + return securityManager; + } + // @Bean({"redisSessionDAO"}) + // public RedisSessionDAO redisSessionDAO() { + // RedisSessionDAO rsd = new RedisSessionDAO(); + // return rsd; + // } + // @Bean({"sessionManager"}) + // public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) { + // DefaultWebSessionManager sm = new DefaultWebSessionManager(); + // sm.setSessionDAO(redisSessionDAO); + // sm.setSessionValidationInterval(10800000L); + // sm.setGlobalSessionTimeout(10800000L); + // sm.setSessionValidationSchedulerEnabled(Boolean.FALSE); + // sm.setSessionIdUrlRewritingEnabled(Boolean.FALSE); + // sm.setSessionIdCookieEnabled(true); + // sm.setSessionIdCookie(this.sessionIdCookie()); + // return sm; + // } + + private SimpleCookie sessionIdCookie() { + SimpleCookie cookie = new SimpleCookie(); + cookie.setName("JSESSIONID"); + cookie.setHttpOnly(true); + cookie.setMaxAge(10800000); + return cookie; + } + + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { + ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); + shiroFilterFactoryBean.setSecurityManager(securityManager); + Map filterChainDefinitionMap = new LinkedHashMap(); + filterChainDefinitionMap.put("/logout", "logout"); + filterChainDefinitionMap.put("/static/**", "anon"); + filterChainDefinitionMap.put("/*.html", "anon"); + filterChainDefinitionMap.put("/*.ico", "anon"); + filterChainDefinitionMap.put("/ajaxLogin", "anon"); + filterChainDefinitionMap.put("/login/**", "anon"); + // if (StringUtils.isNotBlank(this.anonUrls)) { + // String[] anonUrlsList = StringUtils.splitByWholeSeparator(this.anonUrls, ","); + // String[] var5 = anonUrlsList; + // int var6 = anonUrlsList.length; + // for (int var7 = 0; var7 < var6; ++var7) { + // String s = var5[var7]; + // filterChainDefinitionMap.put(s, "anon"); + // } + // } + filterChainDefinitionMap.put("/sync/**", "anon"); + filterChainDefinitionMap.put("/login", "anon"); + filterChainDefinitionMap.put("/captcha/**", "anon"); + filterChainDefinitionMap.put("/doc.html/**", "anon"); + filterChainDefinitionMap.put("/swagger-resources/**", "anon"); + filterChainDefinitionMap.put("/v2/**", "anon"); + filterChainDefinitionMap.put("/webjars/**", "anon"); + filterChainDefinitionMap.put("/swagger-resources/configuration/ui/**", "anon"); + filterChainDefinitionMap.put("/swagger-resources/configuration/security/**", "anon"); + filterChainDefinitionMap.put("/**", "authc"); + shiroFilterFactoryBean.setLoginUrl("/unauth"); + shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + return shiroFilterFactoryBean; + } + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); + return authorizationAttributeSourceAdvisor; + } + +} \ No newline at end of file diff --git a/quick-framework/base-adapter/src/main/java/com/base/adapter/annoation/EnableShiroConfig.java b/quick-framework/base-adapter/src/main/java/com/base/adapter/annoation/EnableShiroConfig.java new file mode 100644 index 00000000..9a6183c3 --- /dev/null +++ b/quick-framework/base-adapter/src/main/java/com/base/adapter/annoation/EnableShiroConfig.java @@ -0,0 +1,19 @@ +package com.base.adapter.annoation; + +import com.base.adapter.ShiroConfig; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * + * @author wangxc + * @date: 2022/1/26 9:52 AM + * + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Import(ShiroConfig.class) +public @interface EnableShiroConfig { +} diff --git a/quick-framework/pom.xml b/quick-framework/pom.xml new file mode 100644 index 00000000..ff21bce7 --- /dev/null +++ b/quick-framework/pom.xml @@ -0,0 +1,21 @@ + + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + 4.0.0 + + quick-framework + pom + + + base-adapter + + + + \ No newline at end of file diff --git a/quick-graphQL/pom.xml b/quick-graphQL/pom.xml new file mode 100644 index 00000000..c5ad30ed --- /dev/null +++ b/quick-graphQL/pom.xml @@ -0,0 +1,47 @@ + + + quick-graphQL + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + + + + com.graphql-java + graphql-java + + + + com.graphql-java + graphql-java-spring-boot-starter-webmvc + + + + com.google.guava + guava + + + + + + + \ No newline at end of file diff --git a/quick-graphQL/src/main/java/com/quick/graphql/GraphQLApplication.java b/quick-graphQL/src/main/java/com/quick/graphql/GraphQLApplication.java new file mode 100644 index 00000000..0981674e --- /dev/null +++ b/quick-graphQL/src/main/java/com/quick/graphql/GraphQLApplication.java @@ -0,0 +1,11 @@ +package com.quick.graphql; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GraphQLApplication { + public static void main(String[] args) { + SpringApplication.run(GraphQLApplication.class, args); + } +} diff --git a/quick-graphQL/src/main/java/com/quick/graphql/config/GraphQLProvider.java b/quick-graphQL/src/main/java/com/quick/graphql/config/GraphQLProvider.java new file mode 100644 index 00000000..3f41f214 --- /dev/null +++ b/quick-graphQL/src/main/java/com/quick/graphql/config/GraphQLProvider.java @@ -0,0 +1,60 @@ +package com.quick.graphql.config; + +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import com.quick.graphql.service.GraphQLDataFetchers; +import graphql.GraphQL; +import graphql.schema.GraphQLSchema; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.SchemaGenerator; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.net.URL; + +import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring; + +@Component +public class GraphQLProvider { + + + @Autowired + GraphQLDataFetchers graphQLDataFetchers; + + private GraphQL graphQL; + + @PostConstruct + public void init() throws IOException { + URL url = Resources.getResource("schema.graphqls"); + String sdl = Resources.toString(url, Charsets.UTF_8); + GraphQLSchema graphQLSchema = buildSchema(sdl); + this.graphQL = GraphQL.newGraphQL(graphQLSchema).build(); + } + + private GraphQLSchema buildSchema(String sdl) { + TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl); + RuntimeWiring runtimeWiring = buildWiring(); + SchemaGenerator schemaGenerator = new SchemaGenerator(); + return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); + } + + private RuntimeWiring buildWiring() { + return RuntimeWiring.newRuntimeWiring() + .type(newTypeWiring("Query") + .dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher())) + .type(newTypeWiring("Book") + .dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher())) + .build(); + } + + @Bean + public GraphQL graphQL() { + return graphQL; + } + +} \ No newline at end of file diff --git a/quick-graphQL/src/main/java/com/quick/graphql/service/GraphQLDataFetchers.java b/quick-graphQL/src/main/java/com/quick/graphql/service/GraphQLDataFetchers.java new file mode 100644 index 00000000..ac18d114 --- /dev/null +++ b/quick-graphQL/src/main/java/com/quick/graphql/service/GraphQLDataFetchers.java @@ -0,0 +1,63 @@ +package com.quick.graphql.service; + +import com.google.common.collect.ImmutableMap; +import graphql.schema.DataFetcher; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@Component +public class GraphQLDataFetchers { + + private static List> books = Arrays.asList( + ImmutableMap.of("id", "book-1", + "name", "Harry Potter and the Philosopher's Stone", + "pageCount", "223", + "authorId", "author-1"), + ImmutableMap.of("id", "book-2", + "name", "Moby Dick", + "pageCount", "635", + "authorId", "author-2"), + ImmutableMap.of("id", "book-3", + "name", "Interview with the vampire", + "pageCount", "371", + "authorId", "author-3") + ); + + private static List> authors = Arrays.asList( + ImmutableMap.of("id", "author-1", + "firstName", "Joanne", + "lastName", "Rowling"), + ImmutableMap.of("id", "author-2", + "firstName", "Herman", + "lastName", "Melville"), + ImmutableMap.of("id", "author-3", + "firstName", "Anne", + "lastName", "Rice") + ); + + public DataFetcher getBookByIdDataFetcher() { + return dataFetchingEnvironment -> { + String bookId = dataFetchingEnvironment.getArgument("id"); + return books + .stream() + .filter(book -> book.get("id").equals(bookId)) + .findFirst() + .orElse(null); + }; + } + + public DataFetcher getAuthorDataFetcher() { + return dataFetchingEnvironment -> { + Map book = dataFetchingEnvironment.getSource(); + String authorId = book.get("authorId"); + return authors + .stream() + .filter(author -> author.get("id").equals(authorId)) + .findFirst() + .orElse(null); + }; + } +} \ No newline at end of file diff --git a/quick-graphQL/src/main/resources/application.properties b/quick-graphQL/src/main/resources/application.properties new file mode 100644 index 00000000..e69de29b diff --git a/quick-graphQL/src/main/resources/schema.graphqls b/quick-graphQL/src/main/resources/schema.graphqls new file mode 100644 index 00000000..8875bafe --- /dev/null +++ b/quick-graphQL/src/main/resources/schema.graphqls @@ -0,0 +1,16 @@ +type Query { + bookById(id: ID): Book +} + +type Book { + id: ID + name: String + pageCount: Int + author: Author +} + +type Author { + id: ID + firstName: String + lastName: String +} \ No newline at end of file diff --git a/quick-hbase/README.md b/quick-hbase/README.md new file mode 100644 index 00000000..a953547f --- /dev/null +++ b/quick-hbase/README.md @@ -0,0 +1,188 @@ +## Hbase简介 + +HBase是Hadoop的生态系统,是建立在Hadoop文件系统(HDFS)之上的分布式、面向列的数据库,通过利用Hadoop的文件系统提供容错能力。如果你需要进行实时读写或者随机访问大规模的数据集的时候,请考虑使用HBase! + +HBase作为Google Bigtable的开源实现,Google Bigtable利用GFS作为其文件存储系统类似,则HBase利用Hadoop HDFS作为其文件存储系统;Google通过运行MapReduce来处理Bigtable中的海量数据,同样,HBase利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable利用Chubby作为协同服务,HBase利用Zookeeper作为对应。 + +### HDFS与HBase比较 + +|HDFS|HBase| +|---|---| +|HDFS适于存储大容量文件的分布式文件系统。|HBase是建立在HDFS之上的数据库。| +|HDFS不支持快速单独记录查找。|HBase提供在较大的表快速查找| +|HDFS提供了高延迟批量处理;没有批处理概念。|HBase提供了数十亿条记录低延迟访问单个行记录(随机存取)。| +|HDFS提供的数据只能顺序访问。|HBase内部使用哈希表和提供随机接入,并且其存储索引,可将在HDFS文件中的数据进行快速查找。| + +### 特征 +- 大:一个表可以有数十亿行,上百万列; +- 无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列; +- 面向列:面向列(族)的存储和权限控制,列(族)独立检索; +- 稀疏:空(null)列并不占用存储空间,表可以设计的非常稀疏; +- 数据多版本:每个单元中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳; +- 数据类型单一:Hbase中的数据都是字符串,没有类型。 + +### 一些概念 + + - RowKey:是Byte array,是表中每条记录的“主键”,方便快速查找,Rowkey的设计非常重要。 + - Column Family:列族,拥有一个名称(string),包含一个或者多个相关列 + - Column:属于某一个columnfamily,familyName:columnName,每条记录可动态添加 + - Version Number:类型为Long,默认值是系统时间戳,可由用户自定义 + - Value(Cell):Byte array + +## Docker安装步骤 + +### 拉取镜像 + +Dockerhub上有很多镜像,找了一个不错的https://hub.docker.com/r/harisekhon/hbase + +一般能用到的镜像,这哥们都给整理了https://github.com/HariSekhon/Dockerfiles + +`docker pull harisekhon/hbase` + +### 启动脚本 + +```bash +docker run -d -h docker-hbase \ + -p 2181:2181 \ + -p 8080:8080 \ + -p 8085:8085 \ + -p 9090:9090 \ + -p 9000:9000 \ + -p 9095:9095 \ + -p 16000:16000 \ + -p 16010:16010 \ + -p 16201:16201 \ + -p 16301:16301 \ + -p 16020:16020\ + --name hbase \ + harisekhon/hbase + +``` +简单说一下 +```text +-d 表示后台运行 +-h 该容器的host为docker-hbase +-p 宿主机端口:容器端口 +--name 该容器的名字 +``` +可以看下[简易教程](https://blog.csdn.net/qqHJQS/column/info/33078) + + +|节点|端口号|协议|使用|说明| +|:---:|---|---|---|---| +|zookeeper|2181||zkCli.sh-serverzookeeper1:2181|客户端接入| +|zookeeper|2888,3888|||集群内部通讯| +|HDFSNamenode|9000|HDFS|hdfsdfs-lshdfs://namenode1:9000/|客户端接入 +|HDFSNamenode|50070|HTTP|http://namenode1:50070/|集群监控| +|HDFSSecondaryNamenode|50090|HTTP|http://namenode1:50090/|secondary监控| +|HDFSDatanode|50010|||客户端接入/其他节点接入| +|HDFSDatanode|50020|||| +|HDFSDatanode|50075|HTTP|http://datanode1:50075/|节点监控| +|HBaseMaster|16000||hbase-client-1.x.x.jar|RegionServer接入| +|HBaseMaster|16010|HTTP|http://namenode1:16010/|集群监控| +|HBaseRegionServer|16020|||客户端接入| +|HBaseRegionServer|16030|HTTP|http://datanode1:16030/|节点监控| + +hbase对应的端口(harisekhon/hbase 修改了默认端口:) +```text +# Stargate 8080 / 8085 +# Thrift 9090 / 9095 +# HMaster 16000 / 16010 +# RS 16201 / 16301 +EXPOSE 2181 8080 8085 9090 9095 16000 16010 16201 16301 + +``` + +### 设置host +在服务的host文件中设置如下映射 +```text +127.0.0.1 docker-hbase +``` + +### 界面 +http://localhost:16010/master-status + + +![图示](http://cdn.wangxc.club/20190716203049.png) + +可在(Gist)[https://gist.github.com/7163422771a9f1ba64991fc7ac5f7087#file-note-md]上查看 + +## 常用命令 +容器起来后,我们进入容器内部,执行`docker exec -ti e60a300f7749 /bin/bash` +然后再执行`hbase shell`得到如下 +```bash +bash-4.4# hbase shell +2019-07-20 08:47:19,876 WARN [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable +HBase Shell +Use "help" to get list of supported commands. +Use "exit" to quit this interactive shell. +For Reference, please visit: http://hbase.apache.org/2.0/book.html#shell +Version 2.1.1, rb60a92d6864ef27295027f5961cb46f9162d7637, Fri Oct 26 19:27:03 PDT 2018 +Took 0.0222 seconds +hbase(main):001:0> +``` + +我们可以使用`help 'create'`的方法查看某命令的使用帮助 + +- 查看集群状态`status` +- 查看hbase版本`version` +- 查看所有表`list` +- 创建表 `create 't1','[Column Family 1]','[Column Family 2]'` + +例如创建一个菜谱表,调味料为一个列族(类),食材则为另一个列族(类),脚本为`create 'cookbook','seasoning','ngredients'` + +- 查看表的结构`describe 't1'` +- 删除表 +`disable 't1'` 之后 `drop 't1'` +- 检查表是否存在`exists 't1'` +- 获取表中所有数据`scan 't1'` +-获取表中前10行数据`scan 't1',{LIMIT=>10}` +- 获取指定column(ad:pv)的前10行数据`scan 't1',{COLUMNS=>['ad:pv'],LIMIT=>10}` +- 加Filter:如过滤rowkey以“2015”开头的前10行数据 `scan 't1',{FILTER=>"PrefixFilter('2015')",LIMIT=>10}` +- 查询rowkey=001下所有的数据`get 't1','001'` +- 查询rowkey=001下family=ad,column=pv`get 't1','001','ad:pv'` 或者 `get 't1','001',{COLUMN=>'ad:pv'}` +- 添加数据`put ,,,,` + +例如:向’t1’表中添加rowkey=002,family=ad,column=pv,value=1000,时间戳默认 +`put 't1','002','ad:pv','1000'` +- 删除rowkey=002的所有数据`deleteall 't1','002'` +- 删除rowkey=002中ad:pv数据`delete 't1','002','ad:pv'` +- 清空表`truncate 't1'` +- 统计行数`count 't1'` +- 查询表t1中的行数,每100条显示一次`count 't1',{INTERVAL=>100}` + +## springboot整合hbase +这里使用的是hbase-client +```bash +. +├── README.md +├── pom.xml +├── quick-hbase.iml +└── src + ├── main + │   ├── java + │   │   └── com + │   │   └── quick + │   │   └── hbase + │   │   ├── HbaseApplication.java + │   │   └── config + │   │   ├── HBaseClient.java + │   │   ├── HbaseConfig.java + │   │   └── HbaseProperties.java + │   └── resources + │   ├── application.yml + │   └── log4j.properties + └── test + └── java + └── com + └── quick + └── hbase + └── config + └── HBaseClientTest.java + +``` + +## 相关参考 +[w3cSchool](https://www.w3cschool.cn/hbase_doc/hbase_doc-vxnl2k1n.html) + +[Hbase原理、基本概念、基本架构](https://cloud.tencent.com/developer/article/1018571) diff --git a/quick-hbase/pom.xml b/quick-hbase/pom.xml new file mode 100644 index 00000000..ac42c118 --- /dev/null +++ b/quick-hbase/pom.xml @@ -0,0 +1,67 @@ + + + quick-hbase + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.1.6.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + 2.1.1 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.slf4j + slf4j-api + 1.7.21 + + + org.slf4j + slf4j-log4j12 + 1.7.21 + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.hbase + hbase-client + ${hbase.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/quick-hbase/src/main/java/com/quick/hbase/HbaseApplication.java b/quick-hbase/src/main/java/com/quick/hbase/HbaseApplication.java new file mode 100644 index 00000000..e4744aca --- /dev/null +++ b/quick-hbase/src/main/java/com/quick/hbase/HbaseApplication.java @@ -0,0 +1,17 @@ +package com.quick.hbase; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2019/7/16 下午8:51 + * https://blog.csdn.net/vbirdbest/article/details/88410954 + */ +@SpringBootApplication +public class HbaseApplication { + public static void main(String[] args) { + SpringApplication.run(HbaseApplication.class); + } +} diff --git a/quick-hbase/src/main/java/com/quick/hbase/config/HBaseClient.java b/quick-hbase/src/main/java/com/quick/hbase/config/HBaseClient.java new file mode 100644 index 00000000..20f8da21 --- /dev/null +++ b/quick-hbase/src/main/java/com/quick/hbase/config/HBaseClient.java @@ -0,0 +1,274 @@ +package com.quick.hbase.config; + +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CompareOperator; +import org.apache.hadoop.hbase.TableExistsException; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.*; +import org.apache.hadoop.hbase.filter.RowFilter; +import org.apache.hadoop.hbase.filter.SubstringComparator; +import org.apache.hadoop.hbase.util.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableMap; + +@DependsOn("hbaseConfig") +@Component +public class HBaseClient { + + @Autowired + private HbaseConfig config; + + private static Connection connection = null; + private static Admin admin = null; + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + @PostConstruct + private void init() { + if (connection != null) { + return; + } + try { + connection = ConnectionFactory.createConnection(config.configuration()); + admin = connection.getAdmin(); + } catch (IOException e) { + logger.error("HBase create connection failed: {}", e); + } + } + + /** + * create 'tableName','[Column Family 1]','[Column Family 2]' + * @param tableName + * @param columnFamilies 列族名 + * @throws IOException + */ + public void createTable(String tableName, String... columnFamilies) throws IOException { + TableName name = TableName.valueOf(tableName); + boolean isExists = this.tableExists(tableName); + if (isExists) { + throw new TableExistsException(tableName + "is exists!"); + } + TableDescriptorBuilder descriptorBuilder = TableDescriptorBuilder.newBuilder(name); + List columnFamilyList = new ArrayList<>(); + for (String columnFamily : columnFamilies) { + ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder + .newBuilder(columnFamily.getBytes()).build(); + columnFamilyList.add(columnFamilyDescriptor); + } + descriptorBuilder.setColumnFamilies(columnFamilyList); + TableDescriptor tableDescriptor = descriptorBuilder.build(); + admin.createTable(tableDescriptor); + } + + /** + * put ,,,, + * @param tableName + * @param rowKey + * @param columnFamily + * @param column + * @param value + * @throws IOException + */ + public void insertOrUpdate(String tableName, String rowKey, String columnFamily, String column, String value) + throws IOException { + this.insertOrUpdate(tableName, rowKey, columnFamily, new String[]{column}, new String[]{value}); + } + + /** + * put ,,,, + * @param tableName + * @param rowKey + * @param columnFamily + * @param columns + * @param values + * @throws IOException + */ + public void insertOrUpdate(String tableName, String rowKey, String columnFamily, String[] columns, String[] values) + throws IOException { + Table table = connection.getTable(TableName.valueOf(tableName)); + Put put = new Put(Bytes.toBytes(rowKey)); + for (int i = 0; i < columns.length; i++) { + put.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(columns[i]), Bytes.toBytes(values[i])); + table.put(put); + } + } + + /** + * @param tableName + * @param rowKey + * @throws IOException + */ + public void deleteRow(String tableName, String rowKey) throws IOException { + Table table = connection.getTable(TableName.valueOf(tableName)); + Delete delete = new Delete(rowKey.getBytes()); + table.delete(delete); + } + + /** + * @param tableName + * @param rowKey + * @param columnFamily + * @throws IOException + */ + public void deleteColumnFamily(String tableName, String rowKey, String columnFamily) throws IOException { + Table table = connection.getTable(TableName.valueOf(tableName)); + Delete delete = new Delete(rowKey.getBytes()); + delete.addFamily(Bytes.toBytes(columnFamily)); + table.delete(delete); + } + + /** + * delete 'tableName','rowKey','columnFamily:column' + * @param tableName + * @param rowKey + * @param columnFamily + * @param column + * @throws IOException + */ + public void deleteColumn(String tableName, String rowKey, String columnFamily, String column) throws IOException { + Table table = connection.getTable(TableName.valueOf(tableName)); + Delete delete = new Delete(rowKey.getBytes()); + delete.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column)); + table.delete(delete); + } + + /** + * disable 'tableName' 之后 drop 'tableName' + * @param tableName + * @throws IOException + */ + public void deleteTable(String tableName) throws IOException { + boolean isExists = this.tableExists(tableName); + if (!isExists) { + return; + } + TableName name = TableName.valueOf(tableName); + admin.disableTable(name); + admin.deleteTable(name); + } + + /** + * get 'tableName','rowkey','family:column' + * @param tableName + * @param rowkey + * @param family + * @param column + * @return + */ + public String getValue(String tableName, String rowkey, String family, String column) { + Table table = null; + String value = ""; + if (StringUtils.isBlank(tableName) || StringUtils.isBlank(family) || StringUtils.isBlank(rowkey) || StringUtils + .isBlank(column)) { + return null; + } + try { + table = connection.getTable(TableName.valueOf(tableName)); + Get g = new Get(rowkey.getBytes()); + g.addColumn(family.getBytes(), column.getBytes()); + Result result = table.get(g); + List ceList = result.listCells(); + if (ceList != null && ceList.size() > 0) { + for (Cell cell : ceList) { + value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()); + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + table.close(); + connection.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return value; + } + + /** + * get 'tableName','rowKey' + * @param tableName + * @param rowKey + * @return + * @throws IOException + */ + public String selectOneRow(String tableName, String rowKey) throws IOException { + Table table = connection.getTable(TableName.valueOf(tableName)); + Get get = new Get(rowKey.getBytes()); + Result result = table.get(get); + NavigableMap>> map = result.getMap(); + for (Cell cell : result.rawCells()) { + String row = Bytes.toString(cell.getRowArray()); + String columnFamily = Bytes.toString(cell.getFamilyArray()); + String column = Bytes.toString(cell.getQualifierArray()); + String value = Bytes.toString(cell.getValueArray()); + // 可以通过反射封装成对象(列名和Java属性保持一致) + System.out.println(row); + System.out.println(columnFamily); + System.out.println(column); + System.out.println(value); + } + return null; + } + + /** + * scan 't1',{FILTER=>"PrefixFilter('2015')"} + * @param tableName + * @param rowKeyFilter + * @return + * @throws IOException + */ + public String scanTable(String tableName, String rowKeyFilter) throws IOException { + Table table = connection.getTable(TableName.valueOf(tableName)); + Scan scan = new Scan(); + if (!StringUtils.isEmpty(rowKeyFilter)) { + RowFilter rowFilter = new RowFilter(CompareOperator.EQUAL, new SubstringComparator(rowKeyFilter)); + scan.setFilter(rowFilter); + } + ResultScanner scanner = table.getScanner(scan); + try { + for (Result result : scanner) { + System.out.println(Bytes.toString(result.getRow())); + for (Cell cell : result.rawCells()) { + System.out.println(cell); + } + } + } finally { + if (scanner != null) { + scanner.close(); + } + } + return null; + } + + + /** + * 判断表是否已经存在,这里使用间接的方式来实现 + * + * admin.tableExists() 会报NoSuchColumnFamilyException, 有人说是hbase-client版本问题 + * @param tableName + * @return + * @throws IOException + */ + public boolean tableExists(String tableName) throws IOException { + TableName[] tableNames = admin.listTableNames(); + if (tableNames != null && tableNames.length > 0) { + for (int i = 0; i < tableNames.length; i++) { + if (tableName.equals(tableNames[i].getNameAsString())) { + return true; + } + } + } + return false; + } +} diff --git a/quick-hbase/src/main/java/com/quick/hbase/config/HbaseConfig.java b/quick-hbase/src/main/java/com/quick/hbase/config/HbaseConfig.java new file mode 100644 index 00000000..de73ae3a --- /dev/null +++ b/quick-hbase/src/main/java/com/quick/hbase/config/HbaseConfig.java @@ -0,0 +1,28 @@ +package com.quick.hbase.config; + +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; +import java.util.Set; + +@Configuration +@EnableConfigurationProperties(HbaseProperties.class) +public class HbaseConfig { + private final HbaseProperties properties; + + public HbaseConfig(HbaseProperties properties) { + this.properties = properties; + } + + public org.apache.hadoop.conf.Configuration configuration() { + org.apache.hadoop.conf.Configuration configuration = HBaseConfiguration.create(); + Map config = properties.getConfig(); + Set keySet = config.keySet(); + for (String key : keySet) { + configuration.set(key, config.get(key)); + } + return configuration; + } +} \ No newline at end of file diff --git a/quick-hbase/src/main/java/com/quick/hbase/config/HbaseProperties.java b/quick-hbase/src/main/java/com/quick/hbase/config/HbaseProperties.java new file mode 100644 index 00000000..cd26d7ab --- /dev/null +++ b/quick-hbase/src/main/java/com/quick/hbase/config/HbaseProperties.java @@ -0,0 +1,16 @@ +package com.quick.hbase.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Map; + +@ConfigurationProperties(prefix = "hbase") +public class HbaseProperties { + private Map config; + public Map getConfig() { + return config; + } + public void setConfig(Map config) { + this.config = config; + } +} \ No newline at end of file diff --git a/quick-hbase/src/main/resources/application.yml b/quick-hbase/src/main/resources/application.yml new file mode 100644 index 00000000..c8ff69c8 --- /dev/null +++ b/quick-hbase/src/main/resources/application.yml @@ -0,0 +1,6 @@ +hbase: + config: + hbase.zookeeper.quorum: myhbase + hbase.zookeeper.port: 2181 + hbase.zookeeper.znode: /hbase + hbase.client.keyvalue.maxsize: 1572864000 \ No newline at end of file diff --git a/quick-hbase/src/main/resources/log4j.properties b/quick-hbase/src/main/resources/log4j.properties new file mode 100644 index 00000000..bb4f8636 --- /dev/null +++ b/quick-hbase/src/main/resources/log4j.properties @@ -0,0 +1,64 @@ +###配置日志根Logger +log4j.rootLogger=DEBUG,stdout,file +#ERROR 为严重错误 主要是程序的错误 +#WARN 为一般警告,比如session丢失 +#INFO 为一般要显示的信息,比如登录登出 +#DEBUG 为程序的调试信息 +log4j.additivity.org.apache=true + +###配置日志信息输出目的地Appender +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +#org.apache.log4j.ConsoleAppender(控制台) +#org.apache.log4j.FileAppender(文件) +#org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件) +#org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件) +#org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) +#log4j.appender.error.Target=System.out +###输出ERROR级别以上的日志 +log4j.appender.stdout.threshold=INFO +###配置日志信息的格式(布局) +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +#org.apache.log4j.HTMLLayout(以HTML表格形式布局) +#org.apache.log4j.PatternLayout(可以灵活地指定布局模式) +#org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串) +#org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息) +###配置日志打印的格式格式化日志信息 +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n + +#%m 输出代码中指定的消息 +#%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL +#%r 输出自应用启动到输出该log信息耗费的毫秒数 +#%c 输出所属的类目,通常就是所在类的全名 +#%t 输出产生该日志事件的线程名 +#%n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n” +#%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss , SSS} +#%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数 +#log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file=org.apache.log4j.DailyRollingFileAppender +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.DatePattern='.'yyyy-MM-dd-HH-mm +# '.'yyyy-MM:每月 +# '.'yyyy-ww:每周 +# '.'yyyy-MM-dd:每天 +# '.'yyyy-MM-dd-a:每天两次 +# '.'yyyy-MM-dd-HH:每小时 +# '.'yyyy-MM-dd-HH-mm:每分钟 +#log4j.appender.file.MaxFileSize=1MB +###滚动文件的最大数 +#log4j.appender.file.MaxBackupIndex=8 +log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%-5p](%-30c{1}) [TxId : %X{PtxId} , SpanId : %X{PspanId}] [ET:%X{ENV_TYPE},AN:%X{APP_NAME},SN:%X{SERVICE_NAME},CN:%X{CONTAINER_NAME},CI:%X{CONTAINER_IP}] %m%n +log4j.appender.file.Threshold=DEBUG +###将消息增加到指定文件中,false指将消息覆盖指定的文件内容 +log4j.appender.file.append=true +###日志的保存位置 +#log4j.appender.file.File=E:/logs/file-debug-log.log +log4j.appender.file.File=logs/debug-debug.log +###每天产生一个日志文件 +#log4j.appender.file=org.apache.log4j.DailyRollingFileAppender +#log4j.appender.file.layout=org.apache.log4j.PatternLayout +#log4j.appender.file.maxFileSize=100 +#log4j.appender.file.maxBackupIndex=5 +#log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%-5p](%-30c{1}) [TxId : %X{PtxId} , SpanId : %X{PspanId}] [ET:%X{ENV_TYPE},AN:%X{APP_NAME},SN:%X{SERVICE_NAME},CN:%X{CONTAINER_NAME},CI:%X{CONTAINER_IP}] %m%n +#log4j.appender.file.Threshold=DEBUG +#log4j.appender.file.append=true +#log4j.appender.file.File=E:/logs/debug-log.log \ No newline at end of file diff --git a/quick-hbase/src/test/java/com/quick/hbase/config/HBaseClientTest.java b/quick-hbase/src/test/java/com/quick/hbase/config/HBaseClientTest.java new file mode 100644 index 00000000..5b6b3561 --- /dev/null +++ b/quick-hbase/src/test/java/com/quick/hbase/config/HBaseClientTest.java @@ -0,0 +1,104 @@ +package com.quick.hbase.config; + +import com.quick.hbase.HbaseApplication; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.io.IOException; +import java.lang.annotation.Target; + +import static org.junit.Assert.*; + +/** + * + * @author wangxc + * @date: 2019/7/20 下午5:45 + * + */ +@SpringBootTest(classes = HbaseApplication.class) +@RunWith(SpringJUnit4ClassRunner.class) +public class HBaseClientTest { + + private final static String TABLE = "quick-hbase-table"; + private final static String TABLE_FAM_1 = "quick"; + private final static String TABLE_FAM_2 = "hbase"; + + @Autowired + private HBaseClient hBaseClient; + + @Test + public void createTable() throws IOException { + hBaseClient.createTable(TABLE, TABLE_FAM_1, TABLE_FAM_2); + } + + /** + * 向TABLE中插入一条记录,列族quick下,写了两个key,speed和感觉feel,列族hbase下插入三个key 动作action、时间time和用户user,类似一个日志 + */ + @Test + public void insertOrUpdate() throws IOException { + hBaseClient.insertOrUpdate(TABLE, "1", TABLE_FAM_1, "speed", "1km/h"); + hBaseClient.insertOrUpdate(TABLE, "1", TABLE_FAM_1, "feel", "better"); + hBaseClient.insertOrUpdate(TABLE, "1", TABLE_FAM_2, "action", "create table"); + hBaseClient.insertOrUpdate(TABLE, "1", TABLE_FAM_2, "time", "2019年07月20日17:52:53"); + hBaseClient.insertOrUpdate(TABLE, "1", TABLE_FAM_2, "user", "admin"); + /** + * shell 结果 + * hbase(main):007:0> scan 'quick-hbase-table' + * ROW COLUMN+CELL + * 1 column=hbase:action, timestamp=1563616496366, value=create table + * 1 column=hbase:time, timestamp=1563616496379, value=2019\xE5\xB9\xB407\xE6\x9C\x8820\xE6\x97\xA517:52:53 + * 1 column=hbase:user, timestamp=1563616496384, value=admin + * 1 column=quick:feel, timestamp=1563616496362, value=better + * 1 column=quick:speed, timestamp=1563616496353, value=1km/h + * 1 row(s) + */ + hBaseClient.insertOrUpdate(TABLE, "2", TABLE_FAM_2, "user", "admin"); + + } + + @Test + public void deleteRow() throws IOException { + hBaseClient.deleteRow(TABLE, "2"); + } + + + @Test + public void deleteColumnFamily() throws IOException { + hBaseClient.deleteColumnFamily(TABLE, "1", TABLE_FAM_2); + } + + @Test + public void deleteColumn() throws IOException { + hBaseClient.deleteColumn(TABLE, "1", TABLE_FAM_2, "action"); + } + + @Test + public void deleteTable() throws IOException { + hBaseClient.deleteTable(TABLE); + } + + @Test + public void getValue() { + String result = hBaseClient.getValue(TABLE, "1", TABLE_FAM_2, "time"); + System.out.println(result); + + } + + @Test + public void selectOneRow() throws IOException { + hBaseClient.selectOneRow(TABLE, "1"); + } + + @Test + public void scanTable() throws IOException { + hBaseClient.scanTable(TABLE, "{FILTER=>\"PrefixFilter('2019')\""); + } + + @Test + public void tableExists() throws IOException { + System.out.println(hBaseClient.tableExists(TABLE)); + } +} \ No newline at end of file diff --git a/quick-hmac/pom.xml b/quick-hmac/pom.xml new file mode 100644 index 00000000..eaebec4f --- /dev/null +++ b/quick-hmac/pom.xml @@ -0,0 +1,37 @@ + + + quick-hmac + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.1.6.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + cn.hutool + hutool-all + 5.6.3 + + + + + \ No newline at end of file diff --git a/quick-hmac/src/main/java/com/quick/hmac/HmacApplication.java b/quick-hmac/src/main/java/com/quick/hmac/HmacApplication.java new file mode 100644 index 00000000..436ddb13 --- /dev/null +++ b/quick-hmac/src/main/java/com/quick/hmac/HmacApplication.java @@ -0,0 +1,11 @@ +package com.quick.hmac; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class HmacApplication { + public static void main(String[] args) { + SpringApplication.run(HmacApplication.class); + } +} diff --git a/quick-hmac/src/main/java/com/quick/hmac/controller/PostController.java b/quick-hmac/src/main/java/com/quick/hmac/controller/PostController.java new file mode 100644 index 00000000..da911a6c --- /dev/null +++ b/quick-hmac/src/main/java/com/quick/hmac/controller/PostController.java @@ -0,0 +1,116 @@ +package com.quick.hmac.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; + +/** + * https://www.cnblogs.com/softidea/p/5745369.html + */ +@RestController +@RequestMapping("post") +public class PostController { + + /** + * application/x-www-form-urlencoded + * + * @return + */ + @RequestMapping("formUrlencoded") + public Object formUrlencoded(HttpServletRequest request) { + printReq(request); + return null; + } + + + /** + * multipart/form-data + * + * @return + */ + @RequestMapping("formData") + public Object formData(HttpServletRequest request) { + printReq(request); + + return null; + } + + /** + * application/json + * + * @return + */ + @RequestMapping("bodyJson") + public Object bodyJson(HttpServletRequest request) { + printReq(request); + + return null; + } + + /** + * text/xml + * + * @return + */ + @RequestMapping("textXml") + public Object textXml(HttpServletRequest request) { + + printReq(request); + return null; + } + + private void printReq(HttpServletRequest request) { + //获取请求头信息 + Enumeration headerNames = request.getHeaderNames(); + //使用循环遍历请求头,并通过getHeader()方法获取一个指定名称的头字段 + while (headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + System.out.println(headerName + " : " + request.getHeader(headerName)); + } + System.out.println("============================================================================================"); + //获取请求行的相关信息 + System.out.println("getMethod:" + request.getMethod()); + System.out.println("getQueryString:" + request.getQueryString()); + System.out.println("getProtocol:" + request.getProtocol()); + System.out.println("getContextPath" + request.getContextPath()); + System.out.println("getPathInfo:" + request.getPathInfo()); + System.out.println("getPathTranslated:" + request.getPathTranslated()); + System.out.println("getServletPath:" + request.getServletPath()); + System.out.println("getRemoteAddr:" + request.getRemoteAddr()); + System.out.println("getRemoteHost:" + request.getRemoteHost()); + System.out.println("getRemotePort:" + request.getRemotePort()); + System.out.println("getLocalAddr:" + request.getLocalAddr()); + System.out.println("getLocalName:" + request.getLocalName()); + System.out.println("getLocalPort:" + request.getLocalPort()); + System.out.println("getServerName:" + request.getServerName()); + System.out.println("getServerPort:" + request.getServerPort()); + System.out.println("getScheme:" + request.getScheme()); + System.out.println("getRequestURL:" + request.getRequestURL()); + + + Enumeration temp = request.getParameterNames(); + if (null != temp) { + while (temp.hasMoreElements()) { + String en = (String) temp.nextElement(); + String value = request.getParameter(en); + System.out.println(en + "===>" + value); + } + } +// StringBuffer jb = new StringBuffer(); +// String line = null; +// try { +// BufferedReader reader = request.getReader(); +// while ((line = reader.readLine()) != null) +// jb.append(line); +// } catch (Exception e) { /*report an error*/ } +// +// try { +// System.out.println(JSONUtil.toJsonPrettyStr(jb.toString())); +// } catch (Exception e) { +// // crash and burn +// e.printStackTrace(); +// } + } +} diff --git a/quick-hmac/src/main/resources/application.yml b/quick-hmac/src/main/resources/application.yml new file mode 100644 index 00000000..075915df --- /dev/null +++ b/quick-hmac/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 9082 \ No newline at end of file diff --git a/quick-i18n/README.md b/quick-i18n/README.md new file mode 100644 index 00000000..de7c7d68 --- /dev/null +++ b/quick-i18n/README.md @@ -0,0 +1,78 @@ +# 国际化资源在springboot中的使用 + +### 目录结构 +```bash +. +├── README.md +├── pom.xml +└── src + ├── main + │   ├── java + │   │   └── com + │   │   └── quick + │   │   └── source + │   │   ├── Application.java + │   │   ├── config + │   │   │   └── ResourceBundleConfig.java + │   │   └── controller + │   │   └── ApiController.java + │   └── resources + │   ├── application.properties + │   └── i18n + │   ├── messages.properties + │   ├── messages_en.properties + │   └── messages_zh.properties + └── test + └── java + +``` +留意下资源文件的路径,实在i18n下 + +### 请求示例 + +- `localhost:8080/hello?lang=zh_CN` + +返回 +```bash +操作成功 +``` + +- `localhost:8080/hello?lang=en_US` + +返回 +```bash +action success +``` + +### 配置 +- 两种配置方式,直接在properties中配置 +```text +spring.messages.basename=i18n/messages +spring.messages.encoding=UTF-8 +``` +- 或者自己创建Bean,在Bean中直接指定 +```java +@Bean +public ResourceBundleMessageSource messageSource() { + Locale.setDefault(Locale.CHINESE); + ResourceBundleMessageSource source = new ResourceBundleMessageSource(); + source.setBasenames("i18n/messages");// name of the resource bundle + source.setUseCodeAsDefaultMessage(true); + source.setDefaultEncoding("UTF-8"); + return source; +} +``` + +### springboot 切换语言可能会报错,记得加上下面Bean的声明 + +```java +@Bean +public LocaleResolver localeResolver(){ + final SessionLocaleResolver localeResolver = new SessionLocaleResolver(); + //final CookieLocaleResolver localeResolver = new CookieLocaleResolver(); + localeResolver.setDefaultLocale(new Locale("zh", "CN")); + return localeResolver; +} +``` +一定要注意这个bean的名字,是`localeResolver`,也可以这样 +`@Bean(name="localeResolver")` \ No newline at end of file diff --git a/quick-i18n/pom.xml b/quick-i18n/pom.xml new file mode 100644 index 00000000..cbabd187 --- /dev/null +++ b/quick-i18n/pom.xml @@ -0,0 +1,30 @@ + + + quick-i18n + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.1.6.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + \ No newline at end of file diff --git a/quick-i18n/src/main/java/com/quick/source/Application.java b/quick-i18n/src/main/java/com/quick/source/Application.java new file mode 100644 index 00000000..502ae63c --- /dev/null +++ b/quick-i18n/src/main/java/com/quick/source/Application.java @@ -0,0 +1,17 @@ +package com.quick.source; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2020/2/18 下午2:46 + * + */ +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class); + } +} diff --git a/quick-i18n/src/main/java/com/quick/source/config/ResourceBundleConfig.java b/quick-i18n/src/main/java/com/quick/source/config/ResourceBundleConfig.java new file mode 100644 index 00000000..f556fe0a --- /dev/null +++ b/quick-i18n/src/main/java/com/quick/source/config/ResourceBundleConfig.java @@ -0,0 +1,46 @@ +package com.quick.source.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; + +import java.util.Locale; + +/** + * + * @author wangxc + * @date: 2020/2/18 下午3:03 + * + */ +@Configuration +public class ResourceBundleConfig { + @Bean + public ResourceBundleMessageSource messageSource() { + Locale.setDefault(Locale.CHINESE); + ResourceBundleMessageSource source = new ResourceBundleMessageSource(); + /** + * 也可以在application.properties中添加配置 + * spring.messages.basename=i18n/messages + * spring.messages.encoding=UTF-8 + */ + source.setBasenames("i18n/messages");// name of the resource bundle + source.setUseCodeAsDefaultMessage(true); + source.setDefaultEncoding("UTF-8"); + return source; + } + + /** + * springboot 中切换语言,需要使用下面的bean,注意bean的name + * 一定要是localeResolver + * @return + */ + @Bean + public LocaleResolver localeResolver(){ + final SessionLocaleResolver localeResolver = new SessionLocaleResolver(); + //final CookieLocaleResolver localeResolver = new CookieLocaleResolver(); + localeResolver.setDefaultLocale(new Locale("zh", "CN")); + return localeResolver; + } +} diff --git a/quick-i18n/src/main/java/com/quick/source/controller/ApiController.java b/quick-i18n/src/main/java/com/quick/source/controller/ApiController.java new file mode 100644 index 00000000..24ea1692 --- /dev/null +++ b/quick-i18n/src/main/java/com/quick/source/controller/ApiController.java @@ -0,0 +1,52 @@ +package com.quick.source.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.http.HttpRequest; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.support.RequestContextUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Locale; + +/** + * + * @author wangxc + * @date: 2020/2/18 下午3:05 + * + */ +@RestController +public class ApiController { + + @Autowired + private MessageSource messageSource; + + @RequestMapping("/hello") + public String hello(@RequestParam(value = "lang") String lang, + HttpServletRequest request, HttpServletResponse response) { + + LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request); + Locale locale = new Locale("zh","CN"); + switch (lang) { + case "zh_CN": + System.out.println("切换到中文"); + break; + case "en_US": + System.out.println("change language to English"); + locale = new Locale("en","US"); + break; + default: + System.out.println("设置默认语言为中文"); + // 默认设置中文 + } + localeResolver.setLocale(request,response,locale); + + String message = messageSource.getMessage("success", null, locale); + return message; + } +} diff --git a/quick-i18n/src/main/resources/application.properties b/quick-i18n/src/main/resources/application.properties new file mode 100644 index 00000000..9425ad84 --- /dev/null +++ b/quick-i18n/src/main/resources/application.properties @@ -0,0 +1,5 @@ +# +# +# +#spring.messages.basename=i18n/messages +#spring.messages.encoding=UTF-8 \ No newline at end of file diff --git a/quick-i18n/src/main/resources/i18n/messages.properties b/quick-i18n/src/main/resources/i18n/messages.properties new file mode 100644 index 00000000..d38979ee --- /dev/null +++ b/quick-i18n/src/main/resources/i18n/messages.properties @@ -0,0 +1,2 @@ +faild=操作失败 +success=操作成功 \ No newline at end of file diff --git a/quick-i18n/src/main/resources/i18n/messages_en.properties b/quick-i18n/src/main/resources/i18n/messages_en.properties new file mode 100644 index 00000000..b2e6cb6f --- /dev/null +++ b/quick-i18n/src/main/resources/i18n/messages_en.properties @@ -0,0 +1,2 @@ +faild=action faild +success=action success \ No newline at end of file diff --git a/quick-i18n/src/main/resources/i18n/messages_zh.properties b/quick-i18n/src/main/resources/i18n/messages_zh.properties new file mode 100644 index 00000000..d38979ee --- /dev/null +++ b/quick-i18n/src/main/resources/i18n/messages_zh.properties @@ -0,0 +1,2 @@ +faild=操作失败 +success=操作成功 \ No newline at end of file diff --git a/quick-idea/code-template/settings.jar.bak b/quick-idea/code-template/settings.jar.bak deleted file mode 100644 index 25a2732d..00000000 Binary files a/quick-idea/code-template/settings.jar.bak and /dev/null differ diff --git a/quick-idea/gitignore-template/gitignore b/quick-idea/gitignore-template/gitignore deleted file mode 100644 index fe4faec8..00000000 --- a/quick-idea/gitignore-template/gitignore +++ /dev/null @@ -1,92 +0,0 @@ -### Java template -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.ear - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: -.idea/workspace.xml -.idea/tasks.xml -.idea/dictionaries -.idea/vcs.xml -.idea/jsLibraryMappings.xml - -# Sensitive or high-churn files: -.idea/dataSources.ids -.idea/dataSources.xml -.idea/dataSources.local.xml -.idea/sqlDataSources.xml -.idea/dynamic.xml -.idea/uiDesigner.xml - -# Gradle: -.idea/gradle.xml -.idea/ - -# Mongo Explorer plugin: -.idea/mongoSettings.xml - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -/out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties -### Windows template -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# Windows shortcuts -*.lnk -### Maven template -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# ignore eclipse files -.project -.classpath -.settings -.metadata \ No newline at end of file diff --git a/quick-idea/gitignore-template/gitignore2.txt b/quick-idea/gitignore-template/gitignore2.txt deleted file mode 100644 index 712db97d..00000000 --- a/quick-idea/gitignore-template/gitignore2.txt +++ /dev/null @@ -1,42 +0,0 @@ -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.ear - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - - -target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -nbproject/private/ -builds/ -nbbuild/ -dist/ -nbdist/ -.nb-gradle/ - -### log -logs/ -log/ diff --git a/quick-img2txt/pom.xml b/quick-img2txt/pom.xml index 2ef5d4ce..7a77a690 100644 --- a/quick-img2txt/pom.xml +++ b/quick-img2txt/pom.xml @@ -60,7 +60,7 @@ commons-io commons-io - 2.4 + 2.7 diff --git a/quick-img2txt/src/main/java/com/quick/controller/Img2TxtController.java b/quick-img2txt/src/main/java/com/quick/controller/Img2TxtController.java index e26fe98a..2fc7c44c 100644 --- a/quick-img2txt/src/main/java/com/quick/controller/Img2TxtController.java +++ b/quick-img2txt/src/main/java/com/quick/controller/Img2TxtController.java @@ -50,7 +50,7 @@ public ResponseEntity imt2txt(@RequestParam("file") Multipa HttpHeaders headers = new HttpHeaders(); // 支持jpg、png - if(originalFilename.endsWith("jpg")||originalFilename.endsWith("png")){ + if(originalFilename.toLowerCase().endsWith("jpg")||originalFilename.toLowerCase().endsWith("png")){ File outFile = img2TxtService.save(file.getBytes(), originalFilename); headers.add("Cache-Control", "no-cache, no-store, must-revalidate"); headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", outFile.getName())); diff --git a/quick-img2txt/src/main/java/com/quick/img2txt/Img2TxtService.java b/quick-img2txt/src/main/java/com/quick/img2txt/Img2TxtService.java index 7b900846..abda0cbd 100644 --- a/quick-img2txt/src/main/java/com/quick/img2txt/Img2TxtService.java +++ b/quick-img2txt/src/main/java/com/quick/img2txt/Img2TxtService.java @@ -21,9 +21,20 @@ public class Img2TxtService { public static String toChar = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:, ^`'. "; public static int width = 150, height = 150; // 大小自己可设置 - @Value("${upload.file.path}") - private String filePath; + private static String FILE_PATH; + static { + try { + FILE_PATH = ResourceUtils.getURL("classpath:").getPath(); + System.out.println(FILE_PATH); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + +// @Value("${upload.file.path}") +// private String filePath; +// @Value("${error.file.path}") private String errPath; @@ -38,7 +49,7 @@ public String getErrorPath(){ // } public File save(byte[] bytes,String name) throws IOException { - File newFile = new File(filePath + File.separator + name); + File newFile = new File(FILE_PATH + File.separator + name); if(!newFile.exists()){ newFile.createNewFile(); } diff --git a/quick-img2txt/src/main/java/com/quick/verificationCode/VerificationCodeService.java b/quick-img2txt/src/main/java/com/quick/verificationCode/VerificationCodeService.java index a8a00414..aae29f49 100644 --- a/quick-img2txt/src/main/java/com/quick/verificationCode/VerificationCodeService.java +++ b/quick-img2txt/src/main/java/com/quick/verificationCode/VerificationCodeService.java @@ -14,7 +14,7 @@ public class VerificationCodeService { public static void main(String[] args) { try { - File file = ResourceUtils.getFile("classpath:code.jpg"); + File file = ResourceUtils.getFile("classpath:code2.png"); code2txt(file); } catch (FileNotFoundException e) { e.printStackTrace(); diff --git a/quick-img2txt/src/main/resources/application.properties b/quick-img2txt/src/main/resources/application.properties index b5d074f5..b03f6244 100644 --- a/quick-img2txt/src/main/resources/application.properties +++ b/quick-img2txt/src/main/resources/application.properties @@ -7,7 +7,7 @@ logging.config=classpath:log4j2.xml #multipart.maxRequestSize=2Mb #upload.file.path=/mnt/data/txt2img -upload.file.path=/mnt/data/img2txt +#upload.file.path=/mnt/data/img2txt error.file.path=/mnt/data/img2txt/error.txt ## Freemarker 配置 diff --git a/quick-img2txt/src/main/resources/code2.png b/quick-img2txt/src/main/resources/code2.png new file mode 100644 index 00000000..28f351fc Binary files /dev/null and b/quick-img2txt/src/main/resources/code2.png differ diff --git a/quick-jdbc/pom.xml b/quick-jdbc/pom.xml index 35b1a2d6..49c23741 100644 --- a/quick-jdbc/pom.xml +++ b/quick-jdbc/pom.xml @@ -3,20 +3,16 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> quick-jdbc - com.quick 1.0-SNAPSHOT 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 1.5.6.RELEASE + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml - - 1.8 - - diff --git a/quick-jdbc/src/main/java/com/quick/jdbc/type/SqlServiceTest.java b/quick-jdbc/src/main/java/com/quick/jdbc/type/SqlServiceTest.java new file mode 100644 index 00000000..329e6489 --- /dev/null +++ b/quick-jdbc/src/main/java/com/quick/jdbc/type/SqlServiceTest.java @@ -0,0 +1,77 @@ +package com.quick.jdbc.type; + +import java.sql.*; + +/** + * jdbc+sqlserver 用来快速验证 + * @author vector + * @date: 2019/9/3 0003 10:12 + */ +public class SqlServiceTest { + public static void main(String[] args) throws ClassNotFoundException, SQLException { + // Create a variable for the connection string. + String connectionUrl = "jdbc:sqlserver://xxx:8838;DatabaseName=xxx;user=xxx;password=xx"; + + + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + Connection con = DriverManager.getConnection(connectionUrl); + Statement stmt = con.createStatement(); + String SQL = "SELECT CandidateId as lbdId,TbdObjectId as tbdId FROM xxxTbdCandidates where CandidateId IN ('a765029f-090c-4d3c-899b-1c381bc6bca1',\n" + + " 'aa600512-e9ee-45e0-bb7c-da75e69b6590',\n" + + " 'a564040b-3db6-446c-bb8b-d58746707ada',\n" + + " 'aaa8038c-8214-4f8d-9c57-b6cd769b5ddd',\n" + + " 'aab902f2-58b9-4bcf-9812-863ef6b212b1',\n" + + " '5f7db9ff-d865-4702-b654-aaa800e35761',\n" + + " 'a56403d3-ee54-43c5-a80a-e5bafde73569',\n" + + " 'a98803f4-556c-4373-aa29-138ce1788cca',\n" + + " '6fe8f482-160b-47f7-b693-aab800d438eb',\n" + + " 'aa2d027c-d6d0-4dc8-a6cb-dd4c6a33eb9c',\n" + + " '4cc17b9c-0962-4950-9793-a9a700c54c18',\n" + + " '8c122c2f-131e-460f-8b15-aab00112a6b2',\n" + + " 'aa9b0449-34b1-48c2-8094-445ce05e6292',\n" + + " 'a9460424-23b4-435f-b14b-b5206fb27c9e',\n" + + " 'aa01045c-cd2d-4959-a02b-db5f2aad5e45',\n" + + " 'c56f157d-9673-45c2-824e-aaae00cca2f9',\n" + + " 'a7f203f6-29a0-45b8-82c3-bcd5e107ba59',\n" + + " '8a137851-8c64-4d77-ad33-aa3c00dfb62b',\n" + + " '8f0d3ddf-82b4-4439-b253-aab900ecb7e1',\n" + + " 'aa060421-2b9c-4337-a824-cae4b05bd458',\n" + + " 'a9300382-fd8c-4424-9283-330d37fa7306',\n" + + " 'aa6803f2-8431-43f5-a66d-09659290b348',\n" + + " 'aaac0231-84b9-435c-9851-08707cc8d51a',\n" + + " '7e3bac90-90c8-4c23-9dfb-aa3c016d35fd',\n" + + " 'a9e403fd-bd48-424c-9ff3-531e30cb080b',\n" + + " 'a667031f-5054-4dcf-bfdc-d8a1fc4a9b4c',\n" + + " 'a8400275-19fc-43f5-8e92-e6feba92c84e',\n" + + " 'aa7f0409-b4d6-474e-aceb-cf8a50ba8594',\n" + + " 'a6ae0329-0738-43ec-9baa-d9cc0cba443a',\n" + + " 'a564040a-49a2-4865-8d0c-5461507d7bd3',\n" + + " 'aa8003af-00b9-4e5a-aed1-c64bbebdaf36',\n" + + " 'd285991d-cd4d-48e5-840f-aa3d001a0011',\n" + + " 'c8531964-eff8-4d4a-b6fb-aa3c0106d0ab',\n" + + " 'a5640413-0609-465e-977d-5285a590831c',\n" + + " 'a68e0352-bfad-4e06-8d15-31430a0228cd',\n" + + " 'aab202fb-4380-47c6-8849-bb5a8a6b8116',\n" + + " 'f28ccfa3-fc4f-48f5-a1b2-aa3c0146fa34',\n" + + " 'aa9a02cf-8cd0-4e0e-805a-d78794f34e90',\n" + + " '68b8ae8e-4b5a-4f12-96a3-aa3c00d60b96',\n" + + " 'a5640424-738c-4d78-a3d6-08c0fdf53092',\n" + + " 'aa930345-b210-42c6-ae3e-bad82417e710',\n" + + " 'a6750434-10de-4602-b1fc-c7a52222a920',\n" + + " 'aab50291-c939-430e-bc54-7d05ae4e42e0',\n" + + " 'aa7903df-742d-41ae-a2ba-af6d56e97a15',\n" + + " 'aaae0297-25ad-4116-a087-83990c08cc3c',\n" + + " '3ffa5276-7700-4251-9932-aa3c014664fd',\n" + + " 'aa300344-fc07-45ad-adbf-bb1259fbdaea',\n" + + " 'aa2a0239-7825-46d3-8eb6-6f3bca35730e',\n" + + " 'a5640421-05a0-4678-846e-bb0296331ff1');\n"; + long s = System.nanoTime(); + ResultSet rs = stmt.executeQuery(SQL); + System.out.println((System.nanoTime() - s) / 1_000_000); + + // Iterate through the data in the result set and display it. + while (rs.next()) { + System.out.println(rs.getString("lbdId") + " ---> " + rs.getString("tbdId")); + } + } +} diff --git a/quick-jdbc/src/main/resources/application.properties b/quick-jdbc/src/main/resources/application.properties index f52d9a9c..67456895 100644 --- a/quick-jdbc/src/main/resources/application.properties +++ b/quick-jdbc/src/main/resources/application.properties @@ -1,4 +1,6 @@ spring.datasource.url=jdbc:mysql://localhost:3306/world spring.datasource.username=root spring.datasource.password=root -spring.datasource.driver-class-name=com.mysql.jdbc.Driver \ No newline at end of file +spring.datasource.driver-class-name=com.mysql.jdbc.Driver + + diff --git a/quick-jpa/pom.xml b/quick-jpa/pom.xml new file mode 100644 index 00000000..b6b5b9ea --- /dev/null +++ b/quick-jpa/pom.xml @@ -0,0 +1,58 @@ + + + quick-jpa + com.quick + 1.0-SNAPSHOT + 4.0.0 + 相关参考:http://www.cnblogs.com/ityouknow/p/5891443.html + + + org.springframework.boot + spring-boot-starter-parent + 1.3.2.RELEASE + + + + + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + \ No newline at end of file diff --git a/quick-jpa/src/main/java/com/quick/jpa/Application.java b/quick-jpa/src/main/java/com/quick/jpa/Application.java new file mode 100644 index 00000000..713bf9fd --- /dev/null +++ b/quick-jpa/src/main/java/com/quick/jpa/Application.java @@ -0,0 +1,36 @@ +package com.quick.jpa; + +import com.quick.jpa.entity.JpaTest; +import com.quick.jpa.repository.JpaTestRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import javax.annotation.PostConstruct; +import java.util.Date; + +/** + * @Author: wangxc + * @GitHub: https://github.com/vector4wang + * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents + * @BLOG: http://vector4wang.tk + * @wxid: BMHJQS + */ +@SpringBootApplication +public class Application { + + @Autowired + private JpaTestRepository jpaTestRepository; + + @PostConstruct + public void test() { + JpaTest jpaTest = new JpaTest(); + jpaTest.setName("test"); + jpaTest.setAddTime(new Date()); + jpaTestRepository.save(jpaTest); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/quick-jpa/src/main/java/com/quick/jpa/entity/JpaTest.java b/quick-jpa/src/main/java/com/quick/jpa/entity/JpaTest.java new file mode 100644 index 00000000..2ae0b986 --- /dev/null +++ b/quick-jpa/src/main/java/com/quick/jpa/entity/JpaTest.java @@ -0,0 +1,52 @@ +package com.quick.jpa.entity; + +import javax.persistence.*; +import java.util.Date; + +/** + * @Author: wangxc + * @GitHub: https://github.com/vector4wang + * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents + * @BLOG: http://vector4wang.tk + * @wxid: BMHJQS + */ +@Entity +@Table(name = "jpa_test") +public class JpaTest { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column(name = "name") + private String name; + + @Column(name = "add_time") + private Date addTime; + + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getAddTime() { + return addTime; + } + + public void setAddTime(Date addTime) { + this.addTime = addTime; + } +} diff --git a/quick-jpa/src/main/java/com/quick/jpa/repository/JpaTestRepository.java b/quick-jpa/src/main/java/com/quick/jpa/repository/JpaTestRepository.java new file mode 100644 index 00000000..be565259 --- /dev/null +++ b/quick-jpa/src/main/java/com/quick/jpa/repository/JpaTestRepository.java @@ -0,0 +1,14 @@ +package com.quick.jpa.repository; + +import com.quick.jpa.entity.JpaTest; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * @Author: wangxc + * @GitHub: https://github.com/vector4wang + * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents + * @BLOG: http://vector4wang.tk + * @wxid: BMHJQS + */ +public interface JpaTestRepository extends JpaRepository { +} diff --git a/quick-jpa/src/main/resources/application.properties b/quick-jpa/src/main/resources/application.properties new file mode 100644 index 00000000..c7ddede3 --- /dev/null +++ b/quick-jpa/src/main/resources/application.properties @@ -0,0 +1,8 @@ +spring.datasource.url=jdbc:mysql://localhost:3306/world +spring.datasource.username=root +spring.datasource.password=root +spring.datasource.driver-class-name=com.mysql.jdbc.Driver + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jackson.serialization.indent_output=true \ No newline at end of file diff --git a/quick-tika/pom.xml b/quick-jwt/pom.xml similarity index 51% rename from quick-tika/pom.xml rename to quick-jwt/pom.xml index b1cd8aa4..79aafc64 100644 --- a/quick-tika/pom.xml +++ b/quick-jwt/pom.xml @@ -2,107 +2,96 @@ - quick-tika + quick-jwt com.quick 1.0-SNAPSHOT - jar 4.0.0 + + UTF-8 + UTF-8 + 1.8 + murraco.JwtAuthServiceApp + + org.springframework.boot spring-boot-starter-parent - 1.3.2.RELEASE + 1.5.6.RELEASE - - UTF-8 - UTF-8 - 1.14 - 2.2.2 - + org.springframework.boot spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - + org.springframework.boot - spring-boot-starter-log4j + spring-boot-starter-data-jpa + org.springframework.boot - spring-boot-starter-test + spring-boot-starter-security + + + + org.springframework.security + spring-security-test test - org.apache.tika - tika-core - ${tika.version} + + org.springframework.boot + spring-boot-devtools + true + + + + mysql + mysql-connector-java - org.apache.tika - tika-parsers - ${tika.version} + com.h2database + h2 + runtime + io.springfox springfox-swagger2 - ${swagger.version} + 2.7.0 + io.springfox springfox-swagger-ui - ${swagger.version} + 2.7.0 - - - - net.sf.json-lib - json-lib - 2.4 - jdk15 + + io.jsonwebtoken + jjwt + 0.7.0 - - - - org.slf4j - slf4j-log4j12 - 1.7.22 + + org.modelmapper + modelmapper + 1.1.0 - - - - junit - junit - 4.12 - test - - + org.springframework.boot spring-boot-maven-plugin - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - diff --git a/quick-jwt/src/main/java/com/quick/.DS_Store b/quick-jwt/src/main/java/com/quick/.DS_Store new file mode 100644 index 00000000..2a60e5c1 Binary files /dev/null and b/quick-jwt/src/main/java/com/quick/.DS_Store differ diff --git a/quick-jwt/src/main/java/com/quick/jwt/.DS_Store b/quick-jwt/src/main/java/com/quick/jwt/.DS_Store new file mode 100644 index 00000000..4379d653 Binary files /dev/null and b/quick-jwt/src/main/java/com/quick/jwt/.DS_Store differ diff --git a/quick-jwt/src/main/java/com/quick/jwt/JwtAuthApplication.java b/quick-jwt/src/main/java/com/quick/jwt/JwtAuthApplication.java new file mode 100644 index 00000000..c78359d2 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/JwtAuthApplication.java @@ -0,0 +1,50 @@ +package com.quick.jwt; + +import com.quick.jwt.model.Role; +import com.quick.jwt.model.User; +import com.quick.jwt.service.UserService; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * https://github.com/murraco/spring-boot-jwt/blob/master/README.md + */ +@SpringBootApplication +public class JwtAuthApplication implements CommandLineRunner { + + @Autowired + UserService userService; + + public static void main(String[] args) { + SpringApplication.run(JwtAuthApplication.class, args); + } + + @Bean + public ModelMapper modelMapper() { + return new ModelMapper(); + } + + @Override + public void run(String... params) throws Exception { + User admin = new User(); + admin.setUsername("admin"); + admin.setPassword("admin"); + admin.setEmail("admin@email.com"); + admin.setRoles(new ArrayList(Arrays.asList(Role.ROLE_ADMIN))); + userService.signup(admin); + User client = new User(); + client.setUsername("client"); + client.setPassword("client"); + client.setEmail("client@email.com"); + client.setRoles(new ArrayList(Arrays.asList(Role.ROLE_CLIENT))); + userService.signup(client); + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/config/SwaggerConfig.java b/quick-jwt/src/main/java/com/quick/jwt/config/SwaggerConfig.java new file mode 100644 index 00000000..e92318ca --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/config/SwaggerConfig.java @@ -0,0 +1,53 @@ +package com.quick.jwt.config; + +import com.google.common.base.Predicates; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.Contact; +import springfox.documentation.service.Tag; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; + +@Configuration +@EnableSwagger2 +public class SwaggerConfig { + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2)// + .select()// + .apis(RequestHandlerSelectors.any())// + .paths(Predicates.not(PathSelectors.regex("/error")))// + .build()// + .apiInfo(metadata())// + .useDefaultResponseMessages(false)// + .securitySchemes( + new ArrayList<>(Arrays.asList(new ApiKey("Bearer %token", "Authorization", "Header"))))// + .tags(new Tag("users", "Operations about users"))// + .tags(new Tag("ping", "Just a ping"))// + .genericModelSubstitutes(Optional.class); + + } + + private ApiInfo metadata() { + return new ApiInfoBuilder()// + .title("JSON Web Token Authentication API")// + .description( + "This is a sample JWT authentication service. You can find out more about JWT at [https://jwt.io/](https://jwt.io/). For this sample, you can use the `admin` or `client` users (password: admin and client respectively) to test the authorization filters. Once you have successfully logged in and obtained the token, you should click on the right top button `Authorize` and introduce it with the prefix \"Bearer \".")// + .version("1.0.0")// + .license("MIT License").licenseUrl("http://opensource.org/licenses/MIT")// + .contact(new Contact(null, null, "vector4wang@qq.com"))// + .build(); + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/controller/AppController.java b/quick-jwt/src/main/java/com/quick/jwt/controller/AppController.java new file mode 100644 index 00000000..9c72ab78 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/controller/AppController.java @@ -0,0 +1,22 @@ +package com.quick.jwt.controller; + +import io.swagger.annotations.Api; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * @author wangxc + * @date: 2019/2/27 下午10:23 + * + */ +@RestController +@RequestMapping("/app") +@Api(value = "业务app") +public class AppController { + + @RequestMapping("/test") + public String test() { + return "hello world!"; + } +} diff --git a/quick-jwt/src/main/java/com/quick/jwt/controller/UserController.java b/quick-jwt/src/main/java/com/quick/jwt/controller/UserController.java new file mode 100644 index 00000000..f46cc444 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/controller/UserController.java @@ -0,0 +1,84 @@ +package com.quick.jwt.controller; + +import com.quick.jwt.dto.UserDataDTO; +import com.quick.jwt.dto.UserResponseDTO; +import com.quick.jwt.model.User; +import com.quick.jwt.service.UserService; +import io.swagger.annotations.*; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +@RestController +@RequestMapping("/users") +@Api(tags = "users") +public class UserController { + + @Autowired + private UserService userService; + + @Autowired + private ModelMapper modelMapper; + + @PostMapping("/signin") + @ApiOperation(value = "${UserController.signin}") + @ApiResponses(value = {// + @ApiResponse(code = 400, message = "Something went wrong"), // + @ApiResponse(code = 422, message = "Invalid username/password supplied")}) + public String login(// + @ApiParam("Username") @RequestParam String username, // + @ApiParam("Password") @RequestParam String password) { + return userService.signin(username, password); + } + + @PostMapping("/signup") + @ApiOperation(value = "${UserController.signup}") + @ApiResponses(value = {// + @ApiResponse(code = 400, message = "Something went wrong"), // + @ApiResponse(code = 403, message = "Access denied"), // + @ApiResponse(code = 422, message = "Username is already in use"), // + @ApiResponse(code = 500, message = "Expired or invalid JWT token")}) + public String signup(@ApiParam("Signup User") @RequestBody UserDataDTO user) { + return userService.signup(modelMapper.map(user, User.class)); + } + + @DeleteMapping(value = "/{username}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + @ApiOperation(value = "${UserController.delete}") + @ApiResponses(value = {// + @ApiResponse(code = 400, message = "Something went wrong"), // + @ApiResponse(code = 403, message = "Access denied"), // + @ApiResponse(code = 404, message = "The user doesn't exist"), // + @ApiResponse(code = 500, message = "Expired or invalid JWT token")}) + public String delete(@ApiParam("Username") @PathVariable String username) { + userService.delete(username); + return username; + } + + @GetMapping(value = "/{username}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + @ApiOperation(value = "${UserController.search}", response = UserResponseDTO.class) + @ApiResponses(value = {// + @ApiResponse(code = 400, message = "Something went wrong"), // + @ApiResponse(code = 403, message = "Access denied"), // + @ApiResponse(code = 404, message = "The user doesn't exist"), // + @ApiResponse(code = 500, message = "Expired or invalid JWT token")}) + public UserResponseDTO search(@ApiParam("Username") @PathVariable String username) { + return modelMapper.map(userService.search(username), UserResponseDTO.class); + } + + @GetMapping(value = "/me") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + @ApiOperation(value = "${UserController.me}", response = UserResponseDTO.class) + @ApiResponses(value = {// + @ApiResponse(code = 400, message = "Something went wrong"), // + @ApiResponse(code = 403, message = "Access denied"), // + @ApiResponse(code = 500, message = "Expired or invalid JWT token")}) + public UserResponseDTO whoami(HttpServletRequest req) { + return modelMapper.map(userService.whoami(req), UserResponseDTO.class); + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/dto/UserDataDTO.java b/quick-jwt/src/main/java/com/quick/jwt/dto/UserDataDTO.java new file mode 100644 index 00000000..273fdbd4 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/dto/UserDataDTO.java @@ -0,0 +1,52 @@ +package com.quick.jwt.dto; + +import com.quick.jwt.model.Role; +import io.swagger.annotations.ApiModelProperty; + +import java.util.List; + +public class UserDataDTO { + + @ApiModelProperty(position = 0) + private String username; + @ApiModelProperty(position = 1) + private String password; + + @ApiModelProperty(position = 2) + private String email; + @ApiModelProperty(position = 3) + List roles; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/dto/UserResponseDTO.java b/quick-jwt/src/main/java/com/quick/jwt/dto/UserResponseDTO.java new file mode 100644 index 00000000..3c753b8c --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/dto/UserResponseDTO.java @@ -0,0 +1,51 @@ +package com.quick.jwt.dto; + +import com.quick.jwt.model.Role; +import io.swagger.annotations.ApiModelProperty; + +import java.util.List; + +public class UserResponseDTO { + + @ApiModelProperty(position = 0) + private Integer id; + @ApiModelProperty(position = 1) + private String username; + @ApiModelProperty(position = 2) + private String email; + @ApiModelProperty(position = 3) + List roles; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/exception/CustomException.java b/quick-jwt/src/main/java/com/quick/jwt/exception/CustomException.java new file mode 100644 index 00000000..d3bdcf90 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/exception/CustomException.java @@ -0,0 +1,26 @@ +package com.quick.jwt.exception; + +import org.springframework.http.HttpStatus; + +public class CustomException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final String message; + private final HttpStatus httpStatus; + + public CustomException(String message, HttpStatus httpStatus) { + this.message = message; + this.httpStatus = httpStatus; + } + + @Override + public String getMessage() { + return message; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/exception/GlobalExceptionHandlerController.java b/quick-jwt/src/main/java/com/quick/jwt/exception/GlobalExceptionHandlerController.java new file mode 100644 index 00000000..1f24277c --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/exception/GlobalExceptionHandlerController.java @@ -0,0 +1,48 @@ +package com.quick.jwt.exception; + +import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes; +import org.springframework.boot.autoconfigure.web.ErrorAttributes; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.RequestAttributes; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandlerController { + + @Bean + public ErrorAttributes errorAttributes() { + // Hide exception field in the return object + return new DefaultErrorAttributes() { + @Override + public Map getErrorAttributes(RequestAttributes requestAttributes, + boolean includeStackTrace) { + Map errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace); + errorAttributes.remove("exception"); + return errorAttributes; + } + }; + } + + @ExceptionHandler(CustomException.class) + public void handleCustomException(HttpServletResponse res, CustomException ex) throws IOException { + res.sendError(ex.getHttpStatus().value(), ex.getMessage()); + } + + @ExceptionHandler(AccessDeniedException.class) + public void handleAccessDeniedException(HttpServletResponse res) throws IOException { + res.sendError(HttpStatus.FORBIDDEN.value(), "Access denied"); + } + + @ExceptionHandler(Exception.class) + public void handleException(HttpServletResponse res) throws IOException { + res.sendError(HttpStatus.BAD_REQUEST.value(), "Something went wrong"); + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/model/Role.java b/quick-jwt/src/main/java/com/quick/jwt/model/Role.java new file mode 100644 index 00000000..b8a032e5 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/model/Role.java @@ -0,0 +1,11 @@ +package com.quick.jwt.model; + +import org.springframework.security.core.GrantedAuthority; + +public enum Role implements GrantedAuthority { + ROLE_ADMIN, ROLE_CLIENT; + + public String getAuthority() { + return name(); + } +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/model/User.java b/quick-jwt/src/main/java/com/quick/jwt/model/User.java new file mode 100644 index 00000000..6ff453da --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/model/User.java @@ -0,0 +1,68 @@ +package com.quick.jwt.model; + +import javax.persistence.*; +import javax.validation.constraints.Size; +import java.util.List; + +@Entity +@Table(name = "user") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Size(min = 4, max = 255, message = "Minimum username length: 4 characters") + @Column(unique = true, nullable = false) + private String username; + + @Column(unique = true, nullable = false) + private String email; + + @Size(min = 8, message = "Minimum password length: 8 characters") + private String password; + + @ElementCollection(fetch = FetchType.EAGER) + List roles; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/repository/UserRepository.java b/quick-jwt/src/main/java/com/quick/jwt/repository/UserRepository.java new file mode 100644 index 00000000..6288a8bc --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/repository/UserRepository.java @@ -0,0 +1,17 @@ +package com.quick.jwt.repository; + +import com.quick.jwt.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import javax.transaction.Transactional; + +public interface UserRepository extends JpaRepository { + + boolean existsByUsername(String username); + + User findByUsername(String username); + + @Transactional + void deleteByUsername(String username); + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/security/JwtTokenFilter.java b/quick-jwt/src/main/java/com/quick/jwt/security/JwtTokenFilter.java new file mode 100644 index 00000000..f6487ce5 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/security/JwtTokenFilter.java @@ -0,0 +1,43 @@ +package com.quick.jwt.security; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.quick.jwt.exception.CustomException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.GenericFilterBean; + + +public class JwtTokenFilter extends GenericFilterBean { + + private JwtTokenProvider jwtTokenProvider; + + public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) + throws IOException, ServletException { + String token = jwtTokenProvider.resolveToken((HttpServletRequest) req); + try { + if (token != null && jwtTokenProvider.validateToken(token)) { + Authentication auth = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(auth); + } + } catch (CustomException ex) { + HttpServletResponse response = (HttpServletResponse) res; + response.sendError(ex.getHttpStatus().value(), ex.getMessage()); + return; + } + filterChain.doFilter(req, res); + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/security/JwtTokenFilterConfigurer.java b/quick-jwt/src/main/java/com/quick/jwt/security/JwtTokenFilterConfigurer.java new file mode 100644 index 00000000..bc2c55a3 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/security/JwtTokenFilterConfigurer.java @@ -0,0 +1,22 @@ +package com.quick.jwt.security; + +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +public class JwtTokenFilterConfigurer extends SecurityConfigurerAdapter { + + private JwtTokenProvider jwtTokenProvider; + + public JwtTokenFilterConfigurer(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + public void configure(HttpSecurity http) throws Exception { + JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider); + http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/security/JwtTokenProvider.java b/quick-jwt/src/main/java/com/quick/jwt/security/JwtTokenProvider.java new file mode 100644 index 00000000..a1ec44c6 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/security/JwtTokenProvider.java @@ -0,0 +1,91 @@ +package com.quick.jwt.security; + +import java.util.Base64; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; + +import com.quick.jwt.exception.CustomException; +import com.quick.jwt.model.Role; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + + +@Component +public class JwtTokenProvider { + + /** + * THIS IS NOT A SECURE PRACTICE! For simplicity, we are storing a static key here. Ideally, in a + * microservices environment, this key would be kept on a config-server. + */ + @Value("${security.jwt.token.secret-key:secret-key}") + private String secretKey; + + @Value("${security.jwt.token.expire-length:3600000}") + private long validityInMilliseconds = 3600000; // 1h + + @Autowired + private MyUserDetails myUserDetails; + + @PostConstruct + protected void init() { + secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); + } + + public String createToken(String username, List roles) { + Claims claims = Jwts.claims().setSubject(username); + claims.put("auth", + roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).filter(Objects::nonNull) + .collect(Collectors.toList())); + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + return Jwts.builder()// + .setClaims(claims)// + .setIssuedAt(now)// + .setExpiration(validity)// + .signWith(SignatureAlgorithm.HS256, secretKey)// + .compact(); + } + + public Authentication getAuthentication(String token) { + UserDetails userDetails = myUserDetails.loadUserByUsername(getUsername(token)); + return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + } + + public String getUsername(String token) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); + } + + public String resolveToken(HttpServletRequest req) { + String bearerToken = req.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7, bearerToken.length()); + } + return null; + } + + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR); + } + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/security/MyUserDetails.java b/quick-jwt/src/main/java/com/quick/jwt/security/MyUserDetails.java new file mode 100644 index 00000000..644a5193 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/security/MyUserDetails.java @@ -0,0 +1,34 @@ +package com.quick.jwt.security; + +import com.quick.jwt.model.User; +import com.quick.jwt.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +public class MyUserDetails implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + final User user = userRepository.findByUsername(username); + if (user == null) { + throw new UsernameNotFoundException("User '" + username + "' not found"); + } + return org.springframework.security.core.userdetails.User// + .withUsername(username)// + .password(user.getPassword())// + .authorities(user.getRoles())// + .accountExpired(false)// + .accountLocked(false)// + .credentialsExpired(false)// + .disabled(false)// + .build(); + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/security/WebSecurityConfig.java b/quick-jwt/src/main/java/com/quick/jwt/security/WebSecurityConfig.java new file mode 100644 index 00000000..3d06531d --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/security/WebSecurityConfig.java @@ -0,0 +1,61 @@ +package com.quick.jwt.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // Disable CSRF (cross site request forgery) + http.csrf().disable(); + // No session will be created or used by spring security + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + // Entry points + http.authorizeRequests()//0 + .antMatchers("/users/signin").permitAll()// + .antMatchers("/users/signup").permitAll()// + .antMatchers("/h2-console/**/**").permitAll() + // Disallow everything else.. + .anyRequest().authenticated(); + // If a user try to access a resource without having enough permissions + http.exceptionHandling().accessDeniedPage("/login"); + // Apply JWT + http.apply(new JwtTokenFilterConfigurer(jwtTokenProvider)); + // Optional, if you want to test the API from a browser + // http.httpBasic(); + } + + @Override + public void configure(WebSecurity web) throws Exception { + // Allow swagger to be accessed without authentication + web.ignoring().antMatchers("/v2/api-docs")// + .antMatchers("/swagger-resources/**")// + .antMatchers("/swagger-ui.html")// + .antMatchers("/configuration/**")// + .antMatchers("/webjars/**")// + .antMatchers("/public") + // Un-secure H2 Database (for testing purposes, H2 console shouldn't be unprotected in production) + .and().ignoring().antMatchers("/h2-console/**/**"); + ; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(12); + } + +} \ No newline at end of file diff --git a/quick-jwt/src/main/java/com/quick/jwt/service/UserService.java b/quick-jwt/src/main/java/com/quick/jwt/service/UserService.java new file mode 100644 index 00000000..49e52779 --- /dev/null +++ b/quick-jwt/src/main/java/com/quick/jwt/service/UserService.java @@ -0,0 +1,67 @@ +package com.quick.jwt.service; + +import com.quick.jwt.exception.CustomException; +import com.quick.jwt.model.User; +import com.quick.jwt.repository.UserRepository; +import com.quick.jwt.security.JwtTokenProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; + +@Service +public class UserService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + @Autowired + private AuthenticationManager authenticationManager; + + public String signin(String username, String password) { + try { + authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); + return jwtTokenProvider.createToken(username, userRepository.findByUsername(username).getRoles()); + } catch (AuthenticationException e) { + throw new CustomException("Invalid username/password supplied", HttpStatus.UNPROCESSABLE_ENTITY); + } + } + + public String signup(User user) { + if (!userRepository.existsByUsername(user.getUsername())) { + user.setPassword(passwordEncoder.encode(user.getPassword())); + userRepository.save(user); + return jwtTokenProvider.createToken(user.getUsername(), user.getRoles()); + } else { + throw new CustomException("Username is already in use", HttpStatus.UNPROCESSABLE_ENTITY); + } + } + + public void delete(String username) { + userRepository.deleteByUsername(username); + } + + public User search(String username) { + User user = userRepository.findByUsername(username); + if (user == null) { + throw new CustomException("The user doesn't exist", HttpStatus.NOT_FOUND); + } + return user; + } + + public User whoami(HttpServletRequest req) { + return userRepository.findByUsername(jwtTokenProvider.getUsername(jwtTokenProvider.resolveToken(req))); + } + +} diff --git a/quick-jwt/src/main/resources/application.yml b/quick-jwt/src/main/resources/application.yml new file mode 100644 index 00000000..1144c244 --- /dev/null +++ b/quick-jwt/src/main/resources/application.yml @@ -0,0 +1,36 @@ +spring: + datasource: +# url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + url: jdbc:mysql://localhost:3308/testdb + username: root + password: 123456 + driver-class-name: com.mysql.jdbc.Driver + tomcat: + max-wait: 20000 + max-active: 50 + max-idle: 20 + min-idle: 15 + jpa: + hibernate: + ddl-auto: create-drop +# ddl-auto: update +# properties: +# hibernate: +# dialect: org.hibernate.dialect.MySQLInnoDBDialect +# format_sql: true +# id: +# new_generator_mappings: false + show-sql: true + +security: + jwt: + token: + secret-key: secret-key + expire-length: 36000 + +UserController: + signin: Authenticates user and returns its JWT token. + signup: Creates user and returns its JWT token + delete: Deletes specific user by username + search: Returns specific user by username + me: Returns current user's data \ No newline at end of file diff --git a/quick-kafka/README.md b/quick-kafka/README.md new file mode 100644 index 00000000..dbe4d2a2 --- /dev/null +++ b/quick-kafka/README.md @@ -0,0 +1,145 @@ + +- 第一个挑战是如何收集大量的数据; +- 第二个挑战是分析收集的数据; + + +最近在搭建一个简易的基于Flink的实时推荐系统中用到了kafka,就快速的整合了下并记录在此 + +### 简介 + +#### 什么是Kafka +Apache Kafka是一个分布式发布 - 订阅消息系统和一个强大的队列,可以处理大量的数据,并使您能够将消息从一个端点传递到另一个端点。 Kafka适合离线和在线消息消费。 Kafka消息保留在磁盘上,并在群集内复制以防止数据丢失。 Kafka构建在ZooKeeper同步服务之上。 它与Apache Storm和Spark非常好地集成,用于实时流式数据分析。 + +#### 术语 + +- Broker:Kafka 集群包含一个或多个服务器,这种服务器被称为 broker。 + +- Topic:每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上,但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)。 + +- Partition:Partition 是物理上的概念,每个 Topic 包含一个或多个 Partition。 + +- Producer:负责发布消息到 Kafka broker。 + +- Consumer:消息消费者,向 Kafka broker 读取消息的客户端。 + +- Consumer Group:每个 Consumer 属于一个特定的 Consumer Group(可为每个 Consumer 指定 group name,若不指定 group name 则属于默认的 group)。 + +- Offset: 消息在partition中的偏移量。每一条消息在partition都有唯一的偏移量,消息者可以指定偏移量来指定要消费的消息。 + +#### 特性 + +可扩展性、数据分区、低延迟、处理大量不同消费者 + +#### 容错 +kafka集群,每一个服务器为broker,每一个broker会有多个partition(创建topic后),假如有三个broker,然后一个topic设置了三个分区,那么每一个broker都会拥有一个分区,然后分区中会产生一个leader,在使用 +过程中有消息进来(producer发送数据),leader则会拿到数据,然后会分发(复制)给其他两个随从,最终会保持每一个分区都会存在该消息(类似ES的集群,也许大部分的集群都是这样设计的) + +#### 消息偏移量 + +> The Kafka cluster durably persists all published records—whether or not they have been consumed—using a configurable retention period. For example, if the retention policy is set to two days, then for the two days after a record is published, it is available for consumption, after which it will be discarded to free up space. Kafka's performance is effectively constant with respect to data size so storing data for a long time is not a problem. + +> In fact, the only metadata retained on a per-consumer basis is the offset or position of that consumer in the log. This offset is controlled by the consumer: normally a consumer will advance its offset linearly as it reads records, but, in fact, since the position is controlled by the consumer it can consume records in any order it likes. For example a consumer can reset to an older offset to reprocess data from the past or skip ahead to the most recent record and start consuming from "now". + +https://kafka.apache.org/intro + +Kafka集群持久地保留所有已发布的记录 - 无论它们是否已被消耗 - 使用可配置的保留期。例如,如果保留策略设置为两天,则在发布记录后的两天内,它可供使用,之后将被丢弃以释放空间。Kafka的性能在数据大小方面实际上是恒定的,因此长时间存储数据不是问题。 + +基于每个消费者保留的唯一元数据是该消费者在日志中的偏移或位置。这种偏移由消费者控制:通常消费者在读取记录时会线性地提高其偏移量,但事实上,由于消费者控制位置,它可以按照自己喜欢的任何顺序消费记录。例如,消费者可以重置为较旧的偏移量以重新处理过去的数据,或者跳到最近的记录并从“现在”开始消费。 + +可查看此[博文](https://zhuanlan.zhihu.com/p/31731892) + + +### 使用Docker搭建Kafka开发环境 + + +#### download images +```bash +docker pull wurstmeister/zookeeper +docker pull wurstmeister/kafka +docker pull sheepkiller/kafka-manager +``` + + + +#### run zookepper +```bash +docker run -d --name zookeeper --publish 2181:2181 \ + --volume /etc/localtime:/etc/localtime \ + --restart=always \ + wurstmeister/zookeeper +``` + + +#### run kafka + +```bash +docker run --name kafka \ + -p 9092:9092 \ + --link zookeeper:zookeeper \ + -e KAFKA_ADVERTISED_HOST_NAME=192.168.1.8 \ + -e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 \ + -d wurstmeister/kafka +``` + +#### run kafka manager +```bash +docker run -it --rm -d \ + --link zookeeper:zookeeper \ + -p 9000:9000 \ + -e ZK_HOSTS="zookeeper:2181" \ + -e APPLICATION_SECRET=letmein sheepkiller/kafka-manager +``` + +web ui in `localhost:9000` + +可以再[Gist](https://gist.github.com/vector4wang/5dff5b6e5abcc41f8955d566dedf74e7 )上查看 + + +### SpringBoot整合Kafka +直接看项目 +```bash +. +|-- README.md +|-- pom.xml +|-- quick-kafka.iml +`-- src + |-- main + | |-- java + | | `-- com + | | `-- quick + | | `-- kafka + | | |-- KafkaApplication.java + | | |-- config + | | | |-- KafkaTopicConfiguration.java + | | | `-- KafkaTopicProperties.java + | | |-- controller + | | | `-- ApiController.java + | | `-- service + | | |-- ConsumerService.java + | | `-- ProducerService.java + | `-- resources + | |-- application.yml + | `-- logback.xml + `-- test + `-- java + +``` + +用法很简单,主要是后面参数的配置(具体看个人应用场景了) + +#### 项目启动 + +项目启动后,kafka自动创建了三个topic + +![mark](http://cdn.wangxc.club/image/20190718/Js3bAzjpqd5Q.png) + +在postman中发送一个请求 +![mark](http://cdn.wangxc.club/image/20190718/th9rISBAT1CK.png) +控制台日志显示发送和接受一个消息 + + +### 不错的博文 + +https://zhuanlan.zhihu.com/p/31731892 + + diff --git a/quick-kafka/pom.xml b/quick-kafka/pom.xml new file mode 100644 index 00000000..464bfb3b --- /dev/null +++ b/quick-kafka/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + quick-kafka + com.quick + 1.0-SNAPSHOT + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + + + + org.springframework.kafka + spring-kafka + + + + com.fasterxml.jackson.core + jackson-databind + + + org.projectlombok + lombok + + + + + + + + \ No newline at end of file diff --git a/quick-kafka/src/main/java/com/quick/kafka/.DS_Store b/quick-kafka/src/main/java/com/quick/kafka/.DS_Store new file mode 100644 index 00000000..9e237e78 Binary files /dev/null and b/quick-kafka/src/main/java/com/quick/kafka/.DS_Store differ diff --git a/quick-kafka/src/main/java/com/quick/kafka/KafkaApplication.java b/quick-kafka/src/main/java/com/quick/kafka/KafkaApplication.java new file mode 100644 index 00000000..5de219c4 --- /dev/null +++ b/quick-kafka/src/main/java/com/quick/kafka/KafkaApplication.java @@ -0,0 +1,15 @@ +package com.quick.kafka; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author vector + * @date: 2019/7/18 0018 10:28 + */ +@SpringBootApplication +public class KafkaApplication { + public static void main(String[] args) { + SpringApplication.run(KafkaApplication.class); + } +} diff --git a/quick-kafka/src/main/java/com/quick/kafka/config/KafkaTopicConfiguration.java b/quick-kafka/src/main/java/com/quick/kafka/config/KafkaTopicConfiguration.java new file mode 100644 index 00000000..0f582cf7 --- /dev/null +++ b/quick-kafka/src/main/java/com/quick/kafka/config/KafkaTopicConfiguration.java @@ -0,0 +1,28 @@ +package com.quick.kafka.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author vector + * @date: 2019/7/18 0018 10:44 + */ +@Configuration +@EnableConfigurationProperties(KafkaTopicProperties.class) +public class KafkaTopicConfiguration { + + @Autowired + private KafkaTopicProperties properties; + + @Bean + public String[] kafkaTopicName() { + return properties.getTopicName(); + } + + @Bean + public String topicGroupId() { + return properties.getGroupId(); + } +} diff --git a/quick-kafka/src/main/java/com/quick/kafka/config/KafkaTopicProperties.java b/quick-kafka/src/main/java/com/quick/kafka/config/KafkaTopicProperties.java new file mode 100644 index 00000000..8f566b46 --- /dev/null +++ b/quick-kafka/src/main/java/com/quick/kafka/config/KafkaTopicProperties.java @@ -0,0 +1,31 @@ +package com.quick.kafka.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.io.Serializable; + +/** + * @author vector + * @date: 2019/7/18 0018 10:42 + */ +@ConfigurationProperties("kafka.topic") +public class KafkaTopicProperties implements Serializable { + private String groupId; + private String[] topicName; + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String[] getTopicName() { + return topicName; + } + + public void setTopicName(String[] topicName) { + this.topicName = topicName; + } +} diff --git a/quick-kafka/src/main/java/com/quick/kafka/controller/ApiController.java b/quick-kafka/src/main/java/com/quick/kafka/controller/ApiController.java new file mode 100644 index 00000000..b0270b70 --- /dev/null +++ b/quick-kafka/src/main/java/com/quick/kafka/controller/ApiController.java @@ -0,0 +1,24 @@ +package com.quick.kafka.controller; + +import com.quick.kafka.service.ProducerService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * @author vector + * @date: 2019/7/18 0018 11:15 + */ +@RestController +@RequestMapping("/kafka") +public class ApiController { + + @Resource + private ProducerService producerService; + + @PostMapping("/send/{topic}") + public String send(@PathVariable("topic") String topic, @RequestBody String body) { + producerService.sendMessage(topic, body); + return "send success"; + } +} diff --git a/quick-kafka/src/main/java/com/quick/kafka/controller/SimulationMsgController.java b/quick-kafka/src/main/java/com/quick/kafka/controller/SimulationMsgController.java new file mode 100644 index 00000000..dd9b06c4 --- /dev/null +++ b/quick-kafka/src/main/java/com/quick/kafka/controller/SimulationMsgController.java @@ -0,0 +1,39 @@ +package com.quick.kafka.controller; + +import com.quick.kafka.service.simulation.SimulationService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * 这个是用来模拟生成实时推荐用户所产生的日志消息 + * @author wangxc + * @date: 2019/7/18 下午8:59 + * + */ +@RestController +@RequestMapping("/rec") +public class SimulationMsgController { + + @Resource + private SimulationService simulationService; + + /** + * p_history和p_history的信息 + * + * 消息体 用户id,产品id,timestamp,action + * history 1,1,1563460819,view + * + *con 1,1,1563459420431,view + * + * @param topic + * @param body + * @return + */ + @PostMapping("/send/{topic}") + public String send(@PathVariable("topic")String topic,@RequestBody String body) { + simulationService.send(topic,body); + return "send success"; + } + +} diff --git a/quick-kafka/src/main/java/com/quick/kafka/service/ConsumerService.java b/quick-kafka/src/main/java/com/quick/kafka/service/ConsumerService.java new file mode 100644 index 00000000..da98d701 --- /dev/null +++ b/quick-kafka/src/main/java/com/quick/kafka/service/ConsumerService.java @@ -0,0 +1,30 @@ +package com.quick.kafka.service; + +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +/** + * @author vector + * @date: 2019/7/18 0018 11:12 + */ +@Service +@Slf4j +public class ConsumerService { + + @Autowired + private KafkaTemplate kafkaTemplate; + + @KafkaListener(topics = "#{kafkaTopicName}", groupId = "#{topicGroupId}") + void processMessage(ConsumerRecord record) { + log.info("kafka process msg start"); + log.info("processMessage, topic = {}, msg={}", record.topic(), record.value()); + + + log.info("kafka process msg end"); + } + +} diff --git a/quick-kafka/src/main/java/com/quick/kafka/service/ProducerService.java b/quick-kafka/src/main/java/com/quick/kafka/service/ProducerService.java new file mode 100644 index 00000000..704c0ef4 --- /dev/null +++ b/quick-kafka/src/main/java/com/quick/kafka/service/ProducerService.java @@ -0,0 +1,40 @@ +package com.quick.kafka.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.support.SendResult; +import org.springframework.stereotype.Service; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; + +/** + * @author vector + * @date: 2019/7/18 0018 11:11 + */ +@Service +@Slf4j +public class ProducerService { + + + @Autowired + private KafkaTemplate kafkaTemplate; + + public void sendMessage(String topic, String data) { + log.info("kafka send msg start"); + ListenableFuture> send = kafkaTemplate.send(topic, data); + send.addCallback(new ListenableFutureCallback>() { + @Override + public void onFailure(Throwable throwable) { + log.error("kafka send msg err, ex = {}, topic = {}, data = {}", throwable, topic, data); + } + + @Override + public void onSuccess(SendResult integerStringSendResult) { + log.info("kafka send msg success, topic = {}, data = {}", topic, data); + } + }); + + log.info("kafka send msg end"); + } +} diff --git a/quick-kafka/src/main/java/com/quick/kafka/service/simulation/SimulationService.java b/quick-kafka/src/main/java/com/quick/kafka/service/simulation/SimulationService.java new file mode 100644 index 00000000..dbfb8023 --- /dev/null +++ b/quick-kafka/src/main/java/com/quick/kafka/service/simulation/SimulationService.java @@ -0,0 +1,30 @@ +package com.quick.kafka.service.simulation; + +import com.quick.kafka.service.ProducerService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; + +/** + * + * @author wangxc + * @date: 2019/7/18 下午9:01 + * + */ +@Service +public class SimulationService { + + @Resource + private ProducerService producerService; + + /** + * 只是测试,暂且不对body做验证,默认body一定不为空 + * @param topic + * @param body + */ + public void send(String topic, String body) { + System.out.println(body); + producerService.sendMessage(topic,body); + } +} diff --git a/quick-kafka/src/main/resources/application.yml b/quick-kafka/src/main/resources/application.yml new file mode 100644 index 00000000..07ced987 --- /dev/null +++ b/quick-kafka/src/main/resources/application.yml @@ -0,0 +1,28 @@ + +spring: + kafka: + bootstrap-servers: 192.168.1.7:9092 + + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer + + batch-size: 65536 + buffer-memory: 524288 + + + consumer: + group-id: 0 + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + + +kafka: + topic: + group-id: topicGroupId + topic-name: + - topic1 + - topic2 + - topic3 +server: + port: 8091 \ No newline at end of file diff --git a/quick-kafka/src/main/resources/logback.xml b/quick-kafka/src/main/resources/logback.xml new file mode 100644 index 00000000..7d900d8e --- /dev/null +++ b/quick-kafka/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/quick-kafka/src/test/java/com/quick/kafka/SimulationTest.java b/quick-kafka/src/test/java/com/quick/kafka/SimulationTest.java new file mode 100644 index 00000000..cc9b36d9 --- /dev/null +++ b/quick-kafka/src/test/java/com/quick/kafka/SimulationTest.java @@ -0,0 +1,70 @@ +package com.quick.kafka; + +import com.quick.kafka.service.simulation.SimulationService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.annotation.Resource; +import java.util.Calendar; +import java.util.Date; + +/** + * + * @author wangxc + * @date: 2019/7/21 下午9:50 + * + */ +@SpringBootTest(classes = KafkaApplication.class) +@RunWith(SpringJUnit4ClassRunner.class) +public class SimulationTest { + + @Resource + private SimulationService simulationService; + + @Test + public void testSimulation() { + + + // 20 4 6 9 13 25 + String userId = "1"; + + // 1 2 3 + String pid = "2"; + + + String action = "2"; + + /** + * p_history和p_history的信息 + * + * 消息体 用户id,产品id,timestamp,action + * history 1,1,1563460819,view + * + * con 1,1,1563459420431,view + * + * 1,123,1563799391,2 + * 1,123,1563799393,2 + * 1,124,1563799394,2 + * 1,122,1563799398,2 + * 1,122,1563799412,1 + * 1,122,1563799418,1 + * 1,112,1563799419,1 + * 1,112,1563799420,1 + * 1,112,1563799428,1 + */ + String format = String.format("%s,%s,%s,%s", userId, pid, new Date().getTime()/1000, action); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + simulationService.send("con", format); + format = String.format("%s,%s,%s,%s", userId, pid, new Date().getTime()/1000, action); + simulationService.send("history", format); + + + } + +} diff --git a/quick-liteflow/pom.xml b/quick-liteflow/pom.xml new file mode 100644 index 00000000..01631cb0 --- /dev/null +++ b/quick-liteflow/pom.xml @@ -0,0 +1,30 @@ + + + quick-liteflow + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + 8 + 8 + + + + + + org.springframework.boot + spring-boot-starter + + + + \ No newline at end of file diff --git a/quick-log/README.md b/quick-log/README.md new file mode 100644 index 00000000..2aab10a3 --- /dev/null +++ b/quick-log/README.md @@ -0,0 +1,213 @@ +## 1、AOP使用 +### 1.1 Springboo项目引入 +比较简单,依赖加入start-aop即可 +```xml + + org.springframework.boot + spring-boot-starter-aop + +``` +编写Aspect方法 +```java +@Aspect +@Component +public class WebLogAspect { + + private static final Logger loggger = LogManager.getLogger(WebLogAspect.class); + + @Pointcut("execution(public * com.quick.log..controller.*.*(..))")//两个..代表所有子目录,最后括号里的两个..代表所有参数 + public void logPointCut() { + } + + @Before("logPointCut()") + public void doBefore(JoinPoint joinPoint) throws Throwable { + // 接收到请求,记录请求内容 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + + // 记录下请求内容 + loggger.info("请求地址 : " + request.getRequestURL().toString()); + loggger.info("HTTP METHOD : " + request.getMethod()); + loggger.info("IP : " + request.getRemoteAddr()); + loggger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + + joinPoint.getSignature().getName()); + loggger.info("参数 : " + Arrays.toString(joinPoint.getArgs())); +// loggger.info("参数 : " + joinPoint.getArgs()); + + } + + @AfterReturning(returning = "ret", pointcut = "logPointCut()")// returning的值和doAfterReturning的参数名一致 + public void doAfterReturning(Object ret) throws Throwable { + // 处理完请求,返回内容 + loggger.info("返回值 : " + ret); + } + + @Around("logPointCut()") + public Object doAround(ProceedingJoinPoint pjp) throws Throwable { + long startTime = System.currentTimeMillis(); + Object ob = pjp.proceed();// ob 为方法的返回值 + loggger.info("耗时 : " + (System.currentTimeMillis() - startTime)); + return ob; + } +} +``` + +注意: +- @Before: 在切点之前,织入相关代码; +- @After: 在切点之后,织入相关代码; +- @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景; +- @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理; +- @Around: 在切入点前后织入代码,并且可以自由的控制何时执行切点; +- @Before和 @AfterReturning分别代表请求接入时与请求返回时,这里分别可以打印请求日志和响应内容,耗时在@Around注解的方法中可以打印,如代码所示; + +如果在before和afterreturing输出请求参数与响应内容,再在around里输出耗时的话,对于单线程是没有任何问题,但是对于多线程来说,就会产生日志错位的问题,所以基于此,可以再around里完成所有参数的打印,如下: +```java +@Around("execution(* com.quick.log.controller.*.*(..))") +public Object logTome(ProceedingJoinPoint pjp) throws Throwable { +RequestAttributes ra = RequestContextHolder.getRequestAttributes(); +ServletRequestAttributes sra = (ServletRequestAttributes) ra; +HttpServletRequest request = sra.getRequest(); + + long startTime = System.currentTimeMillis(); + String method = request.getMethod(); + String uri = request.getRequestURI(); + String queryString = request.getQueryString(); + Object[] args = pjp.getArgs(); + String params = ""; + // result的值就是被拦截方法的返回值 + Object result = pjp.proceed(); + try { + long endTime = System.currentTimeMillis(); + //获取请求参数集合并进行遍历拼接 + if (args.length > 0) { + if ("POST".equals(method)) { + Object object = args[0]; + params = JSON.toJSONString(object, SerializerFeature.WriteMapNullValue); + } else if ("GET".equals(method)) { + params = queryString; + } + params = URLDecoder.decode(params, UTF_8); + } + StringBuilder sb = new StringBuilder(1000); + sb.append("DateTime: ").append(DateUtil.getDatetime()).append("\n"); + sb.append("Method : ").append(method).append("\n"); + sb.append("Params : ").append(params).append("\n"); + sb.append("URI : ").append(uri); + sb.append("COST : ").append(endTime - startTime).append("ms"); + + log.info("request log:\n {}", sb); + } catch (Exception e) { + e.printStackTrace(); + log.error("log error !!", e); + } + return result; + } +``` +这样就不会出现日志错乱的问题;但是代码看起来比较累 + + + +### 1.2 SpringMVC 配置 +这里要注意下是在spring-mvc.xml 开启aop代理开关,如下: + +`` + +**因为spring和spring-mvc是两个容器,并且是父子关系,父的能看到子容器实例,而子容器看不到父容器的示例,因为该切面要在controller层操作,所以需要在mvc的配置里开启,如果配置放错位置的话则切面不会生效!** + +对应的切面类参见上面 + +## 2、拦截器使用 + +继承HandlerInterceptor实现,代码如下 +```java +@Slf4j +public class RequestDurationInterceptor implements HandlerInterceptor { + + private NamedThreadLocal timeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime"); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + long beginTime = System.currentTimeMillis(); + timeThreadLocal.set(beginTime); + return HandlerInterceptor.super.preHandle(request, response, handler); + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + String jsonBody = StringUtil.getParamString("requestBody", request.getParameterMap()); + StringBuilder sb = new StringBuilder(1000); + HandlerMethod h = (HandlerMethod) handler; + F10Bo f10Bo = (F10Bo) SecurityUtils.getSubject().getSession().getAttribute("user"); + if (Objects.nonNull(f10Bo)) { + sb.append("USER : ").append(f10Bo.getUserCode()).append("/").append(f10Bo.getUserName()).append("\n"); + } + if (StringUtil.isEmpty(jsonBody)) { + sb.append("DateTime: ").append(DateUtil.getDatetime()).append("\n"); + sb.append("Controller: ").append(h.getBean().getClass().getName()).append("\n"); + sb.append("Method : ").append(h.getMethod().getName()).append("\n"); + sb.append("Params : ").append(StringUtil.getParamString(request.getParameterMap())).append("\n"); + sb.append("URI : ").append(request.getRequestURI()).append("\n"); + } else { + request.setAttribute("requestBody", jsonBody); + sb.append("DateTime: ").append(DateUtil.getDatetime()).append("\n"); + sb.append("Controller: ").append(h.getBean().getClass().getName()).append("\n"); + sb.append("Method : ").append(h.getMethod().getName()).append("\n"); + sb.append("Params : ").append(StringUtil.getParamString(request.getParameterMap())).append("\n"); + sb.append("URI : ").append(request.getRequestURI()).append("\n"); + } + long endTime = System.currentTimeMillis(); + long beginTime = timeThreadLocal.get(); + long consumeTime = endTime - beginTime; + + sb.append("COST : ").append(consumeTime).append("ms"); + log.info("RequestDurationInterceptor request param: \n {} ", sb); + } +} + +``` + +这里使用ThreadLocal解决多线程的问题; + +### 2.1、springmvc的项目要在配置文件spring-mvc.xml配置如下 +```xml + + + + +``` +### 2.2 springboot增加WebMvcConfigurer实现类,如下 +```java +@Configuration +public class WebAppConfigurer implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 可添加多个 + InterceptorRegistration interceptorRegistration = registry.addInterceptor(new RequestDurationInterceptor()); + interceptorRegistration.addPathPatterns("/**"); + // 如果没有对error处理的话,会发现执行两次RequestDurationInterceptor + interceptorRegistration.excludePathPatterns("/error"); + } +} +``` + + +## 3、使用nginx的日志查看耗时 +现在应用是前后分离,前端使用ngxin将请求转发到后台,所以可以从ngxin请求日志中看到耗时,如 + +![](img.png) + +默认的配置就有耗时,当然也可以重新格式化日志,也能实现参数等较复杂的打印; + +但是研发排查问题的时候大部分还是看后台的日志 + +##4、使用apm链路追踪 +云厂商上的服务有各自的应用监控,这样就有系统化的的平台去监控应用各接口的耗时,从controller层一直到dao层都能监控到耗时,但是看不到参数;而且对私有化部署的服务,这种监控显然太重,不适合; + + + + diff --git a/quick-log/img.png b/quick-log/img.png new file mode 100644 index 00000000..74d972c5 Binary files /dev/null and b/quick-log/img.png differ diff --git a/quick-log/pom.xml b/quick-log/pom.xml index 87265640..ccaea742 100644 --- a/quick-log/pom.xml +++ b/quick-log/pom.xml @@ -3,32 +3,40 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> quick-log - com.quick 1.0-SNAPSHOT 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + org.springframework.boot spring-boot-starter-web - - - - org.springframework.boot spring-boot-starter-logging + + + com.quick.component + quick-platform-component + 1.0-SNAPSHOT + + + + cn.hutool + hutool-all + + org.springframework.boot spring-boot-starter-log4j2 @@ -42,6 +50,16 @@ spring-boot-starter-test test + + + org.projectlombok + lombok + + + + com.alibaba + fastjson + diff --git a/quick-log/src/main/java/com/quick/log/Application.java b/quick-log/src/main/java/com/quick/log/Application.java index db98a02e..bb1b91e7 100644 --- a/quick-log/src/main/java/com/quick/log/Application.java +++ b/quick-log/src/main/java/com/quick/log/Application.java @@ -1,18 +1,24 @@ package com.quick.log; +import com.quick.component.enables.QsEnableAroundLog; +import com.quick.component.enables.QsEnableGlobalExceptionHandler; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; /** * @Author: wangxc * @GitHub: https://github.com/vector4wang * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS + * @wxid: B MHJQS */ @SpringBootApplication +@EnableScheduling +@QsEnableGlobalExceptionHandler +@QsEnableAroundLog(value = "execution(public * com.*..service.*.*(..))") public class Application { public static void main(String[] args) { - SpringApplication.run(Application.class,args); - } + SpringApplication.run(Application.class, args); + } } diff --git a/quick-log/src/main/java/com/quick/log/config/WebLogAspect.java b/quick-log/src/main/java/com/quick/log/config/WebLogAspect.java deleted file mode 100644 index 3e45c17d..00000000 --- a/quick-log/src/main/java/com/quick/log/config/WebLogAspect.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.quick.log.config; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.*; -import org.springframework.stereotype.Component; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import java.util.Arrays; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -@Aspect -@Component -public class WebLogAspect { - - private static final Logger loggger = LogManager.getLogger(WebLogAspect.class); - - @Pointcut("execution(public * com.quick.log..controller.*.*(..))")//两个..代表所有子目录,最后括号里的两个..代表所有参数 - public void logPointCut() { - } - - @Before("logPointCut()") - public void doBefore(JoinPoint joinPoint) throws Throwable { - // 接收到请求,记录请求内容 - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - HttpServletRequest request = attributes.getRequest(); - - // 记录下请求内容 - loggger.info("请求地址 : " + request.getRequestURL().toString()); - loggger.info("HTTP METHOD : " + request.getMethod()); - loggger.info("IP : " + request.getRemoteAddr()); - loggger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." - + joinPoint.getSignature().getName()); - loggger.info("参数 : " + Arrays.toString(joinPoint.getArgs())); -// loggger.info("参数 : " + joinPoint.getArgs()); - - } - - @AfterReturning(returning = "ret", pointcut = "logPointCut()")// returning的值和doAfterReturning的参数名一致 - public void doAfterReturning(Object ret) throws Throwable { - // 处理完请求,返回内容 - loggger.info("返回值 : " + ret); - } - - @Around("logPointCut()") - public Object doAround(ProceedingJoinPoint pjp) throws Throwable { - long startTime = System.currentTimeMillis(); - Object ob = pjp.proceed();// ob 为方法的返回值 - loggger.info("耗时 : " + (System.currentTimeMillis() - startTime)); - return ob; - } -} diff --git a/quick-log/src/main/java/com/quick/log/controller/ApiController.java b/quick-log/src/main/java/com/quick/log/controller/ApiController.java index 5763b340..0c78ee3e 100644 --- a/quick-log/src/main/java/com/quick/log/controller/ApiController.java +++ b/quick-log/src/main/java/com/quick/log/controller/ApiController.java @@ -1,10 +1,7 @@ package com.quick.log.controller; import com.quick.log.service.LoggerService; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.HashMap; @@ -30,10 +27,6 @@ public Map shotLog(@RequestParam("name")String name,@RequestParam Map result = new HashMap<>(); result.put("name",name); result.put("age",age); - for(int i=0;i<100000000;i++) { - - loggerService.showLog(); - } return result; } @@ -47,5 +40,16 @@ public Map shotLog(@RequestBody String parms){ return result; } + @RequestMapping("s") + public String ss() { + return "123"; + } + + @GetMapping("/exception") + public String exce() { + System.out.println("异常"); + throw new IllegalArgumentException("异常了"); + } + } diff --git a/quick-log/src/main/java/com/quick/log/service/LoggerService.java b/quick-log/src/main/java/com/quick/log/service/LoggerService.java index e6bb8654..360bacd4 100644 --- a/quick-log/src/main/java/com/quick/log/service/LoggerService.java +++ b/quick-log/src/main/java/com/quick/log/service/LoggerService.java @@ -1,13 +1,10 @@ package com.quick.log.service; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; - /** * @Author: wangxc * @GitHub: https://github.com/vector4wang @@ -16,21 +13,17 @@ * @wxid: BMHJQS */ @Component +@Slf4j public class LoggerService { - private Logger logger = LogManager.getLogger(LoggerService.class); // @PostConstruct public void showLog() { // for (int i = 0; i < 10; i++) { - logger.debug("我是DEBUG日志"); - logger.info("我是INFO日志"); - logger.warn("我是WARN日志"); - logger.error("我是ERROR日志"); - Marker marker = MarkerManager.getMarker("test"); - logger.error(marker,"我是ERROR日志"); - - logger.fatal("我是FATAL日志"); + log.debug("我是DEBUG日志"); + log.info("我是INFO日志"); + log.warn("我是WARN日志"); + log.error("我是ERROR日志"); // } } diff --git a/quick-log/src/main/java/com/quick/log/util/LogExceptionUtil.java b/quick-log/src/main/java/com/quick/log/util/LogExceptionUtil.java new file mode 100644 index 00000000..7150dd53 --- /dev/null +++ b/quick-log/src/main/java/com/quick/log/util/LogExceptionUtil.java @@ -0,0 +1,21 @@ +package com.quick.log.util; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * 有的时候想把Exception 所有的退债信息写在日志里,通过下面方法则可以实现 + */ +public class LogExceptionUtil { + + /** + * 在日志文件中,打印异常堆栈 + * @param e + * @return + */ + public static String LogExceptionStack(Exception e) { + StringWriter errorsWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(errorsWriter)); + return System.getProperty("line.separator", "/n") + errorsWriter.toString(); + } +} diff --git a/quick-log/src/main/resources/application-pro.properties b/quick-log/src/main/resources/application-pro.properties new file mode 100644 index 00000000..8e8042ac --- /dev/null +++ b/quick-log/src/main/resources/application-pro.properties @@ -0,0 +1,4 @@ +logging.config=classpath:log4j2.xml +#logging.level.root=error + +server.port=8081 \ No newline at end of file diff --git a/quick-log/src/main/resources/application.properties b/quick-log/src/main/resources/application.properties index 00c01038..5d736291 100644 --- a/quick-log/src/main/resources/application.properties +++ b/quick-log/src/main/resources/application.properties @@ -1,4 +1,4 @@ logging.config=classpath:log4j2.xml #logging.level.root=error -server.port=80 \ No newline at end of file +server.port=8084 \ No newline at end of file diff --git a/quick-logback/pom.xml b/quick-logback/pom.xml new file mode 100644 index 00000000..bd825ceb --- /dev/null +++ b/quick-logback/pom.xml @@ -0,0 +1,38 @@ + + + quick-logback + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 1.5.2.RELEASE + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/quick-logback/src/main/java/com/logback/Application.java b/quick-logback/src/main/java/com/logback/Application.java new file mode 100644 index 00000000..4a7407d0 --- /dev/null +++ b/quick-logback/src/main/java/com/logback/Application.java @@ -0,0 +1,18 @@ +package com.logback; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Created with IDEA + * User: vector + * Data: 2017/12/6 + * Time: 17:14 + * Description: + */ +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/quick-logback/src/main/java/com/logback/controller/LogBackController.java b/quick-logback/src/main/java/com/logback/controller/LogBackController.java new file mode 100644 index 00000000..a17e07d7 --- /dev/null +++ b/quick-logback/src/main/java/com/logback/controller/LogBackController.java @@ -0,0 +1,29 @@ +package com.logback.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LogBackController { + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + + @RequestMapping(value = "/logback") + @ResponseBody + public String logback() { + for (int i = 0; i < 100000; i++) { + // 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < + // FATAL,如果设置为WARN,则低于WARN的信息都不会输出。 + logger.trace("日志输出 trace"); + logger.debug("日志输出 debug"); + logger.info("日志输出 info"); + logger.warn("日志输出 warn"); + logger.error("日志输出 error"); + } + return "Hello world"; + } + +} \ No newline at end of file diff --git a/quick-logback/src/main/resources/application.properties b/quick-logback/src/main/resources/application.properties new file mode 100644 index 00000000..43ed4a94 --- /dev/null +++ b/quick-logback/src/main/resources/application.properties @@ -0,0 +1,4 @@ +server.port=8080 + +# TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF +logging.level.*=INFO \ No newline at end of file diff --git a/quick-logback/src/main/resources/logback-spring.xml b/quick-logback/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..7a4efb65 --- /dev/null +++ b/quick-logback/src/main/resources/logback-spring.xml @@ -0,0 +1,80 @@ + + + logback + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + + + + + + + + + + logs/app.log + + logs/app-%d{yyyy-MM-dd}.log + 30 + 1GB + + + + + + + + %d{yyyy-mm-dd HH:mm:ss.SSS} [%thread] %-5level %logger{16} - %msg%n + + + + + + ERROR + + logs/error.log + + logs/error-%d{yyyy-MM-dd}.log + 30 + 1GB + + + %d{yyyy-mm-dd HH:mm:ss.SSS} [%thread] %-5level %logger{16} - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quick-lombok/pom.xml b/quick-lombok/pom.xml new file mode 100644 index 00000000..efa7bbf2 --- /dev/null +++ b/quick-lombok/pom.xml @@ -0,0 +1,57 @@ + + + quick-lombok + com.quick + 1.0-SNAPSHOT + 4.0.0 + + 官方Doc:https://projectlombok.org/features/all + + + + org.projectlombok + lombok + 1.18.2 + + + + org.slf4j + slf4j-api + 1.7.30 + + + org.slf4j + slf4j-simple + 1.7.30 + + + + + org.apache.commons + commons-lang3 + 3.4 + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.0 + + 1.8 + 1.8 + + + + + + + + \ No newline at end of file diff --git a/quick-lombok/src/main/java/com/lombok/BuilderTestMain.java b/quick-lombok/src/main/java/com/lombok/BuilderTestMain.java new file mode 100644 index 00000000..abf665e5 --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/BuilderTestMain.java @@ -0,0 +1,17 @@ +package com.lombok; + +import com.lombok.entity.Adult; + +/** + * @Builder:用在类、构造器、方法上,为你提供复杂的builder APIs,让你可以像如下方式一样调用Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build(); + * @Singular + * @author vector + * @date: 2019/2/27 0027 10:50 + */ +public class BuilderTestMain { + public static void main(String[] args) { + Adult build = Adult.builder().age(10).cd("q").cd("e").cd("w").name("zxc").build(); + System.out.println(build); + + } +} diff --git a/quick-lombok/src/main/java/com/lombok/CleanupTestMain.java b/quick-lombok/src/main/java/com/lombok/CleanupTestMain.java new file mode 100644 index 00000000..dd87583c --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/CleanupTestMain.java @@ -0,0 +1,41 @@ +package com.lombok; + +import lombok.Cleanup; + +import java.io.*; + +/** + * @author vector + * `@Cleanup:自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成try-finally这样的代码来关闭流 + * @date: 2019/2/27 0027 10:03 + */ +public class CleanupTestMain { + public static void main(String[] args) { + + } + + public static void cleanLB() { +// try { +// @Cleanup InputStream inputStream = new FileInputStream(new File("")); +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } + } + + public static void clean() { + InputStream inputStream = null; + try { + inputStream = new FileInputStream(new File("")); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/quick-lombok/src/main/java/com/lombok/DataTestMain.java b/quick-lombok/src/main/java/com/lombok/DataTestMain.java new file mode 100644 index 00000000..97e0a78c --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/DataTestMain.java @@ -0,0 +1,20 @@ +package com.lombok; + +import com.lombok.entity.Student; + +/** + * @author vector + * @date: 2019/2/27 0027 10:30 + */ +public class DataTestMain { + public static void main(String[] args) { + Student student = new Student(); + student.setClassNo("1"); + student.setStuNo("asdf"); + student.setAge(20); + student.setAddress("sasfasdf"); + + + System.out.println(student); + } +} diff --git a/quick-lombok/src/main/java/com/lombok/GetSetTestMain.java b/quick-lombok/src/main/java/com/lombok/GetSetTestMain.java new file mode 100644 index 00000000..78eefb14 --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/GetSetTestMain.java @@ -0,0 +1,39 @@ +package com.lombok; + +import com.lombok.entity.Person; + +import java.util.ArrayList; +import java.util.Date; + +/** + * @Getter/@Setter:用在属性上,再也不用自己手写setter和getter方法了,还可以指定访问范围 + * @ToString:用在类上,可以自动覆写toString方法,当然还可以加其他参数,例如@ToString(exclude=”id”)排除id属性,或者@ToString(callSuper=true, includeFieldNames=true)调用父类的toString方法,包含所有属性 + * @EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法 + * @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:用在类上,自动生成无参构造和使用所有参数的构造函数以及把所有@NonNull属性作为参数的构造函数,如果指定staticName = “of”参数,同时还会生成一个返回类对象的静态工厂方法,比使用构造函数方便很多 + * + * @Data:注解在类上,相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstrutor这些注解,对于POJO类十分有用 + * @author vector + * @date: 2019/2/27 0027 10:05 + */ +public class GetSetTestMain { + public static void main(String[] args) { + Person persion = new Person(); + + + persion.setAddress("hahaha"); + //persion.setName("test");// not access + persion.setAge(12); + persion.setBirthday(new Date()); + + + System.out.println(persion.toString()); + + + Person persion1 = new Person("hahah","深圳",11,new Date(),new ArrayList<>()); + + Person wxc = Person.of("wxc"); + System.out.println(wxc); + + + } +} diff --git a/quick-lombok/src/main/java/com/lombok/GetterTestMain.java b/quick-lombok/src/main/java/com/lombok/GetterTestMain.java new file mode 100644 index 00000000..42e068ad --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/GetterTestMain.java @@ -0,0 +1,28 @@ +package com.lombok; + +import lombok.Getter; + +/** + * @Getter(lazy=true):可以替代经典的Double Check Lock样板代码 + * + * @author vector + * @date: 2019/2/27 0027 11:00 + */ +public class GetterTestMain { + + + @Getter(lazy = true) + private final double[] cached = expensive(); + + private double[] expensive() { + double[] result = new double[1000000]; + for (int i = 0; i < result.length; i++) { + result[i] = Math.asin(i); + } + return result; + } + + public static void main(String[] args) { + + } +} diff --git a/quick-lombok/src/main/java/com/lombok/LogTestMain.java b/quick-lombok/src/main/java/com/lombok/LogTestMain.java new file mode 100644 index 00000000..58e72bfd --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/LogTestMain.java @@ -0,0 +1,43 @@ +package com.lombok; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; + +import java.util.Date; + +/** + * @author vector + * @Log:根据不同的注解生成不同类型的log对象,但是实例名称都是log,有六种可选实现类 + * @CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); + * @Log Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName()); + * @Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class); + * @Log4j2 Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); + * @Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class); + * @XSlf4j Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class); + *

+ * 注意,需要引入对应的包 + * @date: 2019/2/27 0027 11:02 + */ +@Slf4j +public class LogTestMain { + + public static void main(String[] args) { + log.trace("test"); + log.debug("test"); + log.info("test"); + log.warn("test"); + log.error("test"); + + Date date = null; + try { + System.out.println(date.getTime()); + } catch (Exception e) { + e.printStackTrace(); + log.info("=========================================="); + log.error("error : {} ", e.getMessage()); + log.error("error : {} ", e); + log.error("error : {},{} ", 1, e); + log.error("error: {}", ExceptionUtils.getStackTrace(e)); + } + } +} diff --git a/quick-lombok/src/main/java/com/lombok/NonNullTestMain.java b/quick-lombok/src/main/java/com/lombok/NonNullTestMain.java new file mode 100644 index 00000000..d5fe6bb3 --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/NonNullTestMain.java @@ -0,0 +1,32 @@ +package com.lombok; + +import lombok.NonNull; + +/** + * `@NonNull` 给方法参数增加这个注解会自动在方法内对该参数进行是否为空的校验,如果为空,则抛出NPE(NullPointerException) + * + * @author vector + * @date: 2019/2/27 0027 9:59 + */ +public class NonNullTestMain { + public static void main(String[] args) { +// notNullLB(null); + notNull(null); + } + + /** + * Exception in thread "main" java.lang.NullPointerException: arg + * @param arg + */ + public static void notNullLB(@NonNull String arg) { + System.out.println(arg.length()); + } + + /** + * Exception in thread "main" java.lang.NullPointerException + * @param arg + */ + public static void notNull(String arg) { + System.out.println(arg.length()); + } +} diff --git a/quick-lombok/src/main/java/com/lombok/SneakyThrowsTestMain.java b/quick-lombok/src/main/java/com/lombok/SneakyThrowsTestMain.java new file mode 100644 index 00000000..86b34a84 --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/SneakyThrowsTestMain.java @@ -0,0 +1,39 @@ +package com.lombok; + +import lombok.SneakyThrows; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +/** + * + * @SneakyThrows:自动抛受检异常,而无需显式在方法上使用throws语句 + * @author vector + * @date: 2019/2/27 0027 10:57 + */ +public class SneakyThrowsTestMain { + + @SneakyThrows() + public void read() { + InputStream inputStream = new FileInputStream(""); + } + @SneakyThrows + public void write() { + throw new UnsupportedEncodingException(); + } + + //相当于 + /** + + * public void read() throws FileNotFoundException { + * InputStream inputStream = new FileInputStream(""); + * } + * public void write() throws UnsupportedEncodingException { + * throw new UnsupportedEncodingException(); + * } + * + */ + +} diff --git a/quick-lombok/src/main/java/com/lombok/SynchronizedTestMain.java b/quick-lombok/src/main/java/com/lombok/SynchronizedTestMain.java new file mode 100644 index 00000000..70356542 --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/SynchronizedTestMain.java @@ -0,0 +1,24 @@ +package com.lombok; + +import lombok.Synchronized; + +/** + * @Synchronized:用在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性$lock或$LOCK,而java中的synchronized关键字锁对象是this,锁在this或者自己的类对象上存在副作用,就是你不能阻止非受控代码去锁this或者类对象,这可能会导致竞争条件或者其它线程错误 + * @author vector + * @date: 2019/2/27 0027 10:59 + */ +public class SynchronizedTestMain { + @Synchronized + public static void hello() { + System.out.println("world"); + } + //相当于 + /** + * private static final Object $LOCK = new Object[0]; + * public static void hello() { + * synchronized ($LOCK) { + * System.out.println("world"); + * } + * } + */ +} diff --git a/quick-lombok/src/main/java/com/lombok/ValTestMain.java b/quick-lombok/src/main/java/com/lombok/ValTestMain.java new file mode 100644 index 00000000..ee8fea8e --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/ValTestMain.java @@ -0,0 +1,22 @@ +package com.lombok; + +import lombok.val; + +import java.util.ArrayList; +import java.util.HashSet; + +/** + * val:用在局部变量前面,相当于将变量声明为final + * @author vector + * @date: 2019/2/27 0027 9:52 + */ +public class ValTestMain { + public static void main(String[] args) { + val sets = new HashSet<>();// ===> final Set sets = new HashSet<>(); + val lists = new ArrayList<>();// ===> final List lists = new ArrayList<>(); + val str = "123";// ===> final String str = "123"; + + System.out.println(str); + + } +} diff --git a/quick-lombok/src/main/java/com/lombok/ValueTestMain.java b/quick-lombok/src/main/java/com/lombok/ValueTestMain.java new file mode 100644 index 00000000..6c14b49a --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/ValueTestMain.java @@ -0,0 +1,15 @@ +package com.lombok; + +import com.lombok.entity.Teacher; + +/** + * @Value:用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法 + * @author vector + * @date: 2019/2/27 0027 10:40 + */ +public class ValueTestMain { + public static void main(String[] args) { + Teacher teacher = new Teacher("wxc"); + // teacher.setSalary() 没有setter方法 + } +} diff --git a/quick-lombok/src/main/java/com/lombok/entity/Adult.java b/quick-lombok/src/main/java/com/lombok/entity/Adult.java new file mode 100644 index 00000000..cda7ef2e --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/entity/Adult.java @@ -0,0 +1,21 @@ +package com.lombok.entity; + +import lombok.Builder; +import lombok.Singular; +import lombok.ToString; + +import java.util.List; + +/** + * @author vector + * @date: 2019/2/27 0027 10:50 + */ +@Builder +@ToString +public class Adult { + private String name; + private int age; + + @Singular + private List cds; +} diff --git a/quick-lombok/src/main/java/com/lombok/entity/Person.java b/quick-lombok/src/main/java/com/lombok/entity/Person.java new file mode 100644 index 00000000..a39550ca --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/entity/Person.java @@ -0,0 +1,34 @@ +package com.lombok.entity; + +import lombok.*; + +import java.util.Date; +import java.util.List; + +/** + * @author vector + * @date: 2019/2/27 0027 10:10 + */ +@ToString(exclude = "age") +@EqualsAndHashCode +@NoArgsConstructor // 无参构造 +@RequiredArgsConstructor(staticName = "of") // @NonNull属性作为参数的构造函数 +@AllArgsConstructor // 所有参数的构造函数 +public class Person { + @Getter(AccessLevel.PRIVATE) + @Setter(AccessLevel.PRIVATE) + @NonNull + private String name; + @Getter + @Setter + private String address; + @Getter + @Setter + private int age; + @Getter + @Setter + private Date birthday; + @Getter + @Setter + private List tmp; +} diff --git a/quick-lombok/src/main/java/com/lombok/entity/Student.java b/quick-lombok/src/main/java/com/lombok/entity/Student.java new file mode 100644 index 00000000..ff1b8ea2 --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/entity/Student.java @@ -0,0 +1,15 @@ +package com.lombok.entity; + +import lombok.Data; +import lombok.ToString; + +/** + * @author vector + * @date: 2019/2/27 0027 10:30 + */ +@Data +@ToString(callSuper = true) +public class Student extends Person { + private String stuNo; + private String classNo; +} diff --git a/quick-lombok/src/main/java/com/lombok/entity/Teacher.java b/quick-lombok/src/main/java/com/lombok/entity/Teacher.java new file mode 100644 index 00000000..f9b58714 --- /dev/null +++ b/quick-lombok/src/main/java/com/lombok/entity/Teacher.java @@ -0,0 +1,14 @@ +package com.lombok.entity; + +import lombok.Value; + +/** + * @Value:用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法 + * @author vector + * @date: 2019/2/27 0027 10:40 + */ +@Value +public class Teacher extends Person { + private String salary; + +} diff --git a/quick-mail/pom.xml b/quick-mail/pom.xml new file mode 100644 index 00000000..8ef1c8e6 --- /dev/null +++ b/quick-mail/pom.xml @@ -0,0 +1,55 @@ + + + quick-mail + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-mail + + + + org.springframework.boot + spring-boot-starter-test + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + \ No newline at end of file diff --git a/quick-mail/src/.DS_Store b/quick-mail/src/.DS_Store new file mode 100644 index 00000000..9d52cd44 Binary files /dev/null and b/quick-mail/src/.DS_Store differ diff --git a/quick-mail/src/main/.DS_Store b/quick-mail/src/main/.DS_Store new file mode 100644 index 00000000..891bd22a Binary files /dev/null and b/quick-mail/src/main/.DS_Store differ diff --git a/quick-mail/src/main/java/.DS_Store b/quick-mail/src/main/java/.DS_Store new file mode 100644 index 00000000..76f6f84d Binary files /dev/null and b/quick-mail/src/main/java/.DS_Store differ diff --git a/quick-mail/src/main/java/com/EmailApplication.java b/quick-mail/src/main/java/com/EmailApplication.java new file mode 100644 index 00000000..d4cca261 --- /dev/null +++ b/quick-mail/src/main/java/com/EmailApplication.java @@ -0,0 +1,17 @@ +package com; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2020/5/6 下午9:50 + * + */ +@SpringBootApplication +public class EmailApplication { + public static void main(String[] args) { + SpringApplication.run(EmailApplication.class); + } +} diff --git a/quick-mail/src/main/java/com/quick/.DS_Store b/quick-mail/src/main/java/com/quick/.DS_Store new file mode 100644 index 00000000..d2034751 Binary files /dev/null and b/quick-mail/src/main/java/com/quick/.DS_Store differ diff --git a/quick-mail/src/main/java/com/quick/mail/service/SendEmailService.java b/quick-mail/src/main/java/com/quick/mail/service/SendEmailService.java new file mode 100644 index 00000000..e809374d --- /dev/null +++ b/quick-mail/src/main/java/com/quick/mail/service/SendEmailService.java @@ -0,0 +1,217 @@ +package com.quick.mail.service; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; +import org.springframework.util.ResourceUtils; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.io.File; +import java.io.FileNotFoundException; + +/** + * + * @author wangxc + * @date: 2020/5/6 下午9:46 + * + */ +@Service +public class SendEmailService { + + + @Autowired + private JavaMailSender javaMailSender; + + @Autowired + private TemplateEngine templateEngine; + + /** + * 文本邮件 + */ + public void sendEmail() { + SimpleMailMessage msg = new SimpleMailMessage(); + msg.setFrom("772704457@qq.com"); + msg.setTo("vector4wang@gmail.com","wdc43101289217@126.com"); + msg.setSubject("Testing from Spring Boot"); + msg.setText("Hello World \n Spring Boot Email"); + javaMailSender.send(msg); + } + + /** + * 带图片 + * @throws MessagingException + * @throws FileNotFoundException + */ + public void sendImageEmail() throws MessagingException, FileNotFoundException { + MimeMessage message = javaMailSender.createMimeMessage(); + + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom("772704457@qq.com"); + helper.setTo(new String[]{"vector4wang@gmail.com","wdc43101289217@126.com"}); + helper.setSubject("Testing from Spring Boot"); + String rscId = "id"; + helper.setText("这是有图片的邮件
", true); + FileSystemResource res = new FileSystemResource(ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "bang.jpeg")); + helper.addInline(rscId, res); + javaMailSender.send(message); + } + + /** + * 带有html的邮件 + */ + public void sendHtmlEmail() throws MessagingException { + MimeMessage message = javaMailSender.createMimeMessage(); + + //true表示需要创建一个multipart message + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom("772704457@qq.com"); + helper.setTo(new String[]{"vector4wang@gmail.com","wdc43101289217@126.com"}); + helper.setSubject("Testing from Spring Boot"); +// String htmlStr = new StringBuilder() +// .append("\n") +// .append("

\n") +// .append("

恭喜你中奖了!

\n") +// .append(" https://github.com/vector4wang/spring-boot-quick").append(" 您的鼓励是我前进的最大动力!
\n") +// .append(" 如果对你有帮助,请点赞收藏\n") +// .append(" \n").append("").toString(); + String htmlContent = getHtmlContent(); + helper.setText(htmlContent, true); + javaMailSender.send(message); + } + + public void sendAttachmentMail() throws MessagingException, FileNotFoundException { + MimeMessage message = javaMailSender.createMimeMessage(); + + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom("772704457@qq.com"); + helper.setTo(new String[]{"vector4wang@gmail.com","wdc43101289217@126.com"}); + helper.setSubject("Testing from Spring Boot"); + helper.setText("带有附件的邮件", true); + File file = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "bang.jpeg"); + FileSystemResource fileSystemResource = new FileSystemResource(file); + helper.addAttachment(file.getName(), fileSystemResource); + + javaMailSender.send(message); + } + + public void sendTemplateEmail() throws MessagingException, FileNotFoundException { + Context context = new Context(); + context.setVariable("project", "quick-springbooot"); + context.setVariable("author", "vector4wang"); + context.setVariable("url", "https://github.com/vector4wang/spring-boot-quick"); + String emailContent = templateEngine.process("wel", context); + + MimeMessage message = javaMailSender.createMimeMessage(); + + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom("772704457@qq.com"); + helper.setTo(new String[]{"vector4wang@gmail.com","wdc43101289217@126.com"}); + helper.setSubject("Testing from Spring Boot"); + helper.setText(emailContent, true); + helper.addInline("bang", ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "bang.jpeg")); + javaMailSender.send(message); + + } + + public String getHtmlContent() { + String str = new StringBuilder().append("\n").append("\n").append("\n") + .append(" \n") + .append(" \n") + .append(" 邮件推送\n").append("\n").append("\n").append("\n") + .append("
\n").append(" 月台安全\n") + .append("
\n").append("
\n").append("
\n") + .append("
事件详情
\n") + .append("
\n").append("
一号仓
\n") + .append("
\n").append(" 事件\n") + .append(" 人员告警\n").append(" |\n") + .append(" 作业车辆\n").append(" \n") + .append("
\n").append("
\n").append("
\n") + .append("
\n").append(" 事件信息\n") + .append("
\n").append("
\n") + .append("
\n").append("
\n") + .append(" 开始时间: \n") + .append(" 2020-3-15 00:00:00\n") + .append("
\n").append("
\n") + .append(" 结束时间: \n") + .append(" 2020-3-16 00:00:00\n") + .append("
\n").append("
\n") + .append("
\n").append(" 持续时长: \n") + .append(" 2.7h\n").append("
\n") + .append("
\n").append("
\n").append("
\n").append("
\n") + .append(" \n").append("
\n").append("
\n") + .append("
事件详情
\n") + .append("
\n").append("
一号仓
\n") + .append("
\n").append(" 事件\n") + .append(" 人员告警\n").append(" |\n") + .append(" 作业车辆\n").append(" \n") + .append("
\n").append("
\n").append("
\n") + .append("
\n").append(" 事件信息\n") + .append("
\n").append("
\n") + .append("
\n").append("
\n") + .append(" 开始时间: \n") + .append(" 2020-3-15 00:00:00\n") + .append("
\n").append("
\n") + .append(" 结束时间: \n") + .append(" 2020-3-16 00:00:00\n") + .append("
\n").append("
\n") + .append("
\n").append(" 持续时长: \n") + .append(" 2.7h\n").append("
\n") + .append("
\n").append("
\n").append("
\n").append("
\n") + .append(" \n").append("
\n").append("
\n") + .append("
事件详情
\n") + .append("
\n").append("
一号仓
\n") + .append("
\n").append(" 事件\n") + .append(" 人员告警\n").append(" |\n") + .append(" 作业车辆\n").append(" \n") + .append("
\n").append("
\n").append("
\n") + .append("
\n").append(" 事件信息\n") + .append("
\n").append("
\n") + .append("
\n").append("
\n") + .append(" 开始时间: \n") + .append(" 2020-3-15 00:00:00\n") + .append("
\n").append("
\n") + .append(" 结束时间: \n") + .append(" 2020-3-16 00:00:00\n") + .append("
\n").append("
\n") + .append("
\n").append(" 持续时长: \n") + .append(" 2.7h\n").append("
\n") + .append("
\n").append("
\n").append("
\n").append("
\n") + .append("\n").append("").toString(); + + return str; + + } + +} diff --git a/quick-mail/src/main/resources/application.properties b/quick-mail/src/main/resources/application.properties new file mode 100644 index 00000000..62c60de8 --- /dev/null +++ b/quick-mail/src/main/resources/application.properties @@ -0,0 +1,20 @@ +spring.mail.host=smtp.qq.com +#spring.mail.host=smtp.gmail.com +spring.mail.username=772704457@qq.com +spring.mail.password=xxxxxxxxx授权码 + +# Other properties +spring.mail.protocol=smtp +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.connectiontimeout=5000 +spring.mail.properties.mail.smtp.timeout=5000 +spring.mail.properties.mail.smtp.writetimeout=5000 +spring.mail.default-encoding=UTF-8 + +# TLS , port 587 +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.starttls.required=true + +# SSL, post 465 +#spring.mail.properties.mail.smtp.socketFactory.port = 465 +#spring.mail.properties.mail.smtp.socketFactory.class = javax.net.ssl.SSLSocketFactory \ No newline at end of file diff --git a/quick-mail/src/main/resources/bang.jpeg b/quick-mail/src/main/resources/bang.jpeg new file mode 100644 index 00000000..44739870 Binary files /dev/null and b/quick-mail/src/main/resources/bang.jpeg differ diff --git a/quick-mail/src/main/resources/templates/wel.html b/quick-mail/src/main/resources/templates/wel.html new file mode 100644 index 00000000..36188256 --- /dev/null +++ b/quick-mail/src/main/resources/templates/wel.html @@ -0,0 +1,13 @@ + + + + + Email + + + +

Craig Walls says...

+Hello Boot! +Hello Boot! + + \ No newline at end of file diff --git a/quick-mail/src/test/java/service/SendEmailServiceTest.java b/quick-mail/src/test/java/service/SendEmailServiceTest.java new file mode 100644 index 00000000..6c5b0798 --- /dev/null +++ b/quick-mail/src/test/java/service/SendEmailServiceTest.java @@ -0,0 +1,57 @@ +package service; + +import com.EmailApplication; +import com.quick.mail.service.SendEmailService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +import javax.annotation.Resource; +import javax.mail.MessagingException; +import java.io.FileNotFoundException; + +/** + * + * @author wangxc + * @date: 2020/5/6 下午9:52 + * + */ +@SpringBootTest(classes = EmailApplication.class) +@RunWith(SpringJUnit4ClassRunner.class) +public class SendEmailServiceTest { + + @Resource + private SendEmailService sendEmailService; + + + + @Test + public void testSendTxtEmail() { + sendEmailService.sendEmail(); + } + + @Test + public void testSendImageEmail() throws MessagingException, FileNotFoundException { + sendEmailService.sendImageEmail(); + } + + @Test + public void testSendHtmlEmail() throws MessagingException { + sendEmailService.sendHtmlEmail(); + } + + @Test + public void testSendAttachmentMail() throws FileNotFoundException, MessagingException { + sendEmailService.sendAttachmentMail(); + } + + @Test + public void testTemplateEmail() throws FileNotFoundException, MessagingException { + sendEmailService.sendTemplateEmail(); + + } +} diff --git a/quick-maven-plugin/pom.xml b/quick-maven-plugin/pom.xml new file mode 100644 index 00000000..67caae74 --- /dev/null +++ b/quick-maven-plugin/pom.xml @@ -0,0 +1,61 @@ + + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + 4.0.0 + + quick-maven-plugin + 1.0-SNAPSHOT + maven-plugin + + + 8 + 8 + UTF-8 + + + + + + + org.apache.maven + maven-plugin-api + 3.0 + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + provided + 3.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quick-maven-plugin/src/main/java/com/quick/plugin/TimerPlugin.java b/quick-maven-plugin/src/main/java/com/quick/plugin/TimerPlugin.java new file mode 100644 index 00000000..45a454c7 --- /dev/null +++ b/quick-maven-plugin/src/main/java/com/quick/plugin/TimerPlugin.java @@ -0,0 +1,30 @@ +package com.quick.plugin; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + + +@Mojo(name = "TimerPlugin") +public class TimerPlugin extends AbstractMojo { + + @Parameter(property = "timer.username",defaultValue = "vector") + private String userName; + + @Parameter(property = "timer.status",defaultValue = "nice") + private String status; + + + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + String currentTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + getLog().info("timer plugin is running,current time is " + currentTime); + getLog().info(String.format("hi %s ! Now you are %s",userName,status)); + } +} diff --git a/quick-method-evaluate/.DS_Store b/quick-method-evaluate/.DS_Store new file mode 100644 index 00000000..c1294c47 Binary files /dev/null and b/quick-method-evaluate/.DS_Store differ diff --git a/quick-method-evaluate/pom.xml b/quick-method-evaluate/pom.xml new file mode 100644 index 00000000..762a54f6 --- /dev/null +++ b/quick-method-evaluate/pom.xml @@ -0,0 +1,53 @@ + + + quick-method-evaluate + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + + com.github.houbb + junitperf + 2.0.0 + + + org.junit.jupiter + junit-jupiter-engine + + + com.github.houbb + log-integration + + + org.freemarker + freemarker + + + com.google.guava + guava + + + org.apache.commons + commons-math3 + + + org.apiguardian + apiguardian-api + + + org.opentest4j + opentest4j + + + org.junit.platform + junit-platform-commons + + + + + + + diff --git a/quick-method-evaluate/src/main/java/com/method/evaluate/SampleUtilsTest.java b/quick-method-evaluate/src/main/java/com/method/evaluate/SampleUtilsTest.java new file mode 100644 index 00000000..8fcc7a80 --- /dev/null +++ b/quick-method-evaluate/src/main/java/com/method/evaluate/SampleUtilsTest.java @@ -0,0 +1,46 @@ +package com.method.evaluate; + +import com.github.houbb.junitperf.core.annotation.JunitPerfConfig; +import com.github.houbb.junitperf.core.report.impl.HtmlReporter; + +import java.util.WeakHashMap; + +/** + * + * @author wangxc + * @date: 2019-11-16 09:43 + * + */ +public class SampleUtilsTest { + + + /** + * 2个线程运行。 + * 准备时间:1000ms + * 运行时间: 2000ms + * @throws InterruptedException if any + */ + @JunitPerfConfig(duration = 2000) + public void junitPerfConfigTest() throws InterruptedException { + System.out.println("junitPerfConfigTest"); + Thread.sleep(200); + } + + @JunitPerfConfig(warmUp = 1000, duration = 5000, reporter = {HtmlReporter.class}) + public void print1Test() { + Long sum = 0L; + for (long i = 0; i < Integer.MAX_VALUE; i++) { + sum += i; + } + System.out.println(sum); + } + + @JunitPerfConfig(duration = 5000) + public void print2Test() { + long sum = 0L; + for (long i = 0; i < Integer.MAX_VALUE; i++) { + sum += i; + } + System.out.println(sum); + } +} diff --git a/quick-modules/README.md b/quick-modules/README.md new file mode 100644 index 00000000..9732d449 --- /dev/null +++ b/quick-modules/README.md @@ -0,0 +1,366 @@ + +# SpringBoot多模块的开发、发布、引用与部署(Nexus3) + + +>历史上的今天,那是在我国古代的这一天,蒙古人铁木真中年得了一种因脱发导致变成秃头的不治之症,因为之前从为见过此病例,所以便以铁木真的名字来命名此病,也就是现在大家都知道的“老铁没毛病”。 + +### 为何模块开发 + +  先举个栗子,同一张数据表,可能要在多个项目中或功能中使用,所以就有可能在每个模块都要搞一个mybatis去配置。如果一开始规定说这张表一定不可以改字段属性,那么没毛病。但是事实上, 一张表从项目开始到结束,不知道被改了多少遍,所以,你有可能在多个项目中去改mybatis改到吐血! +在举一个栗子,一个web服务里包含了多个功能模块,比如其中一个功能可能会消耗大量资源和时间,当用户调用这个功能的时候,可能会影响到其他功能的正常使用,这个时候,如果把各个功能模块分出来单独部署,然后通过http请求去调用,至于性能和响应速度,再单独去优化,整个过程会非常的舒服!这也有利于将来的分布式集群(现在的微服务springcloud或dubbo就是模块化编程的最好示例)。 +  根据当前的业务需求,我需要重构现有的web功能,多模块化,然后单独部署,基本架构示意图如下 +[![WX20180810-230910@2x.png](https://i.loli.net/2018/08/10/5b6daa8e7e5c9.png)](https://i.loli.net/2018/08/10/5b6daa8e7e5c9.png) + + + +### 怎样分模块 +注意:下面配置的步骤是基于IntelliJ IDEA 2016.3.4(64),不保证eclipse能成功。如果你还在使用eclipse,建议你删掉它,使用idea吧 +1、创建maven主项目例如,springbootmodules,并删掉src文件 +2、右键项目分别创建三个module,dao,service1,service2 +3、将之前项目用到的依赖写在主项目的pom里,这里要注意 +4、dao层主要提供实体类,CURD接口和xml映射文件 +5、一定要在service1和service2配置数据库的相关信息,并添加spring的相关配置 +6、编写接口测试 + +### 相关代码 +父项目pom + +本地测试的时候,手残安装了Nexus3,所以使用起来跟Nexus有点不太一样 + +```xml + + + + quick-modules + com.quick + pom + 1.0-SNAPSHOT + + 4.0.0 + + + dao + service1 + service2 + test + + + + 1.8 + 1.3.1 + 5.1.39 + junit + 2.4.2 + 1.9.2 + 1.2 + 1.4 + 1.2.7 + + + + org.springframework.boot + spring-boot-starter-parent + 1.5.1.RELEASE + + + + + + + + + com.quick + quick-modules-dao + ${project.version} + + + + com.quick + quick-modules-service1 + ${project.version} + + + + com.quick + quick-modules-service2 + ${project.version} + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot} + + + + + junit + junit + ${junit.version} + + + org.apache.commons + commons-pool2 + ${commons-pool2.versoin} + + + commons-beanutils + commons-beanutils + ${commons-beanutils.version} + + + + commons-logging + commons-logging + ${commons-logging.version} + + + + commons-dbcp + commons-dbcp + commons-dbcp.version + + + com.alibaba + fastjson + ${fastjson.version} + + + + + + + + + + nexus + Releases + http://nexus.wangxc.club:8081/repository/maven-releases + + + nexus + Snapshot + http://nexus.wangxc.club:8081/repository/maven-snapshots + + + + + + + nexus + Nexus Plugin Repository + http://nexus.wangxc.club:8081/repository/maven-public/ + + true + + + true + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + true + + + + attach-sources + + package + + jar-no-fork + + + + + + + +``` +dao模块的pom(里面配置了mybatis的逆向功能插件) +```xml + + + + springbootquick + com.boot.lean + 1.0-SNAPSHOT + + 4.0.0 + + dao + jar + + + + + + src/main/java + + **/*.xml + + + + src/main/resources + + + + +``` +service1和service2的pom一样 +```xml + + + + quick-modules + com.quick + 1.0-SNAPSHOT + + 4.0.0 + jar + quick-modules-service1 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.quick + quick-modules-dao + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot} + + + + + com.alibaba + fastjson + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +需要注意的是,service模块里我用的是注解配置,如图所示 + +![结构示意图](http://upload-images.jianshu.io/upload_images/3167229-b360e7b2756a0bdf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +注意配置文件里的端口号 + +2018年08月10日23:18:27 已经更新了,上图的配置已经改变,请在github上查看最新代码 +注意service中引入dao模块时候spring对mapper的扫描配置,建议最好加上MapperScan这个注解即使你这个service的包名跟dao里的一样 +``` +@SpringBootApplication +@MapperScan("com.modules.dao") +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class); + } +} +``` + +具体代码[请点我](https://github.com/vector4wang/spring-boot-quick/tree/master/quick-modules) + +### 发布 + +发布的时候需要在maven里的setting配置下权限,参照官网给的[示例](https://help.sonatype.com/repomanager3/maven-repositories),一步到位 +``` + + + + nexus + admin + admin123 + + + + + + nexus + * + http://localhost:8081/repository/maven-public/ + + + + + nexus + + + + + central + http://central + true + true + + + + + central + http://central + true + true + + + + + + + nexus + + +``` +对于dao模块一定要先deploy到私服上才行,在dao根目录执行 +`mvn deploy` 即可 +为了展示如何引用,我创建了单独的test模块,供大家测试使用 +你可以直接运行,也可以打包成jar包运行 + + +### 打包测试 +在父项目下执行maven命令 +```bash +mvn package +``` +service1和service2目录下分别会产生target文件,里面包含可执行jar包,分别执行 +```bash +java -jar service1-1.0-SNAPSHOT +java -jar service2-1.0-SNAPSHOT +``` +如果一切顺利的话,你可以得出下面的操作结果 + +![结果图](http://upload-images.jianshu.io/upload_images/3167229-b68d434f68fcf6a7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +注意端口号哦 + +有什么问题,自行解决,然后你会发现,跨过这个坑,还有无数个坑在等你~在等你~ \ No newline at end of file diff --git a/quick-modules/dao/pom.xml b/quick-modules/dao/pom.xml index 8f73329c..d6342281 100644 --- a/quick-modules/dao/pom.xml +++ b/quick-modules/dao/pom.xml @@ -18,7 +18,10 @@ org.mybatis.spring.boot mybatis-spring-boot-starter - ${mybatis-spring-boot} + + + mysql + mysql-connector-java
@@ -48,7 +51,7 @@ mysql mysql-connector-java - 5.1.30 + ${mysql-connector} diff --git a/quick-modules/dao/src/main/java/com/modules/dao/CityMapper.java b/quick-modules/dao/src/main/java/com/modules/dao/CityMapper.java index 086ae680..307ffa5b 100644 --- a/quick-modules/dao/src/main/java/com/modules/dao/CityMapper.java +++ b/quick-modules/dao/src/main/java/com/modules/dao/CityMapper.java @@ -1,32 +1,16 @@ package com.modules.dao; -import com.modules.domain.City; -import com.modules.domain.CityExample; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; +import com.modules.entity.City; -import java.util.List; - -@Mapper public interface CityMapper { - int countByExample(CityExample example); - - int deleteByExample(CityExample example); - int deleteByPrimaryKey(Integer id); int insert(City record); int insertSelective(City record); - List selectByExample(CityExample example); - City selectByPrimaryKey(Integer id); - int updateByExampleSelective(@Param("record") City record, @Param("example") CityExample example); - - int updateByExample(@Param("record") City record, @Param("example") CityExample example); - int updateByPrimaryKeySelective(City record); int updateByPrimaryKey(City record); diff --git a/quick-modules/dao/src/main/java/com/modules/dao/CityMapper.xml b/quick-modules/dao/src/main/java/com/modules/dao/CityMapper.xml index 5fd5dbcb..8e8cd92d 100644 --- a/quick-modules/dao/src/main/java/com/modules/dao/CityMapper.xml +++ b/quick-modules/dao/src/main/java/com/modules/dao/CityMapper.xml @@ -1,88 +1,16 @@ - + - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - - - - - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - - - - ID, Name, CountryCode, District, Population - - select count(*) from city - - - - - - update city - - - ID = #{record.id,jdbcType=INTEGER}, - - - Name = #{record.name,jdbcType=CHAR}, - - - CountryCode = #{record.countrycode,jdbcType=CHAR}, - - - District = #{record.district,jdbcType=CHAR}, - - - Population = #{record.population,jdbcType=INTEGER}, - - - - - - - - update city - set ID = #{record.id,jdbcType=INTEGER}, - Name = #{record.name,jdbcType=CHAR}, - CountryCode = #{record.countrycode,jdbcType=CHAR}, - District = #{record.district,jdbcType=CHAR}, - Population = #{record.population,jdbcType=INTEGER} - - - - - + update city @@ -194,7 +76,7 @@ where ID = #{id,jdbcType=INTEGER} - + update city set Name = #{name,jdbcType=CHAR}, CountryCode = #{countrycode,jdbcType=CHAR}, diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/mapper/CountryMapper.java b/quick-modules/dao/src/main/java/com/modules/dao/CountryMapper.java similarity index 70% rename from quick-mybatis-druid/src/main/java/com/quick/druid/mapper/CountryMapper.java rename to quick-modules/dao/src/main/java/com/modules/dao/CountryMapper.java index d4de21f0..cbbbe86b 100644 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/mapper/CountryMapper.java +++ b/quick-modules/dao/src/main/java/com/modules/dao/CountryMapper.java @@ -1,9 +1,7 @@ -package com.quick.druid.mapper; +package com.modules.dao; -import com.quick.druid.entity.Country; -import org.apache.ibatis.annotations.Mapper; +import com.modules.entity.Country; -@Mapper public interface CountryMapper { int deleteByPrimaryKey(String code); diff --git a/quick-mybatis-druid/src/main/resources/mapper/CountryMapper.xml b/quick-modules/dao/src/main/java/com/modules/dao/CountryMapper.xml similarity index 93% rename from quick-mybatis-druid/src/main/resources/mapper/CountryMapper.xml rename to quick-modules/dao/src/main/java/com/modules/dao/CountryMapper.xml index 97211049..b19b038f 100644 --- a/quick-mybatis-druid/src/main/resources/mapper/CountryMapper.xml +++ b/quick-modules/dao/src/main/java/com/modules/dao/CountryMapper.xml @@ -1,7 +1,7 @@ - - + + @@ -32,7 +32,7 @@ delete from country where Code = #{code,jdbcType=CHAR} - + insert into country (Code, Name, Continent, Region, SurfaceArea, IndepYear, Population, LifeExpectancy, GNP, @@ -46,7 +46,7 @@ #{headofstate,jdbcType=CHAR}, #{capital,jdbcType=INTEGER}, #{code2,jdbcType=CHAR} ) - + insert into country @@ -143,7 +143,7 @@ - + update country @@ -191,7 +191,7 @@ where Code = #{code,jdbcType=CHAR} - + update country set Name = #{name,jdbcType=CHAR}, Continent = #{continent,jdbcType=CHAR}, diff --git a/quick-modules/dao/src/main/java/com/modules/domain/CityExample.java b/quick-modules/dao/src/main/java/com/modules/domain/CityExample.java deleted file mode 100644 index 429a29ca..00000000 --- a/quick-modules/dao/src/main/java/com/modules/domain/CityExample.java +++ /dev/null @@ -1,530 +0,0 @@ -package com.modules.domain; - -import java.util.ArrayList; -import java.util.List; - -public class CityExample { - protected String orderByClause; - - protected boolean distinct; - - protected List oredCriteria; - - public CityExample() { - oredCriteria = new ArrayList(); - } - - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - - public String getOrderByClause() { - return orderByClause; - } - - public void setDistinct(boolean distinct) { - this.distinct = distinct; - } - - public boolean isDistinct() { - return distinct; - } - - public List getOredCriteria() { - return oredCriteria; - } - - public void or(Criteria criteria) { - oredCriteria.add(criteria); - } - - public Criteria or() { - Criteria criteria = createCriteriaInternal(); - oredCriteria.add(criteria); - return criteria; - } - - public Criteria createCriteria() { - Criteria criteria = createCriteriaInternal(); - if (oredCriteria.size() == 0) { - oredCriteria.add(criteria); - } - return criteria; - } - - protected Criteria createCriteriaInternal() { - Criteria criteria = new Criteria(); - return criteria; - } - - public void clear() { - oredCriteria.clear(); - orderByClause = null; - distinct = false; - } - - protected abstract static class GeneratedCriteria { - protected List criteria; - - protected GeneratedCriteria() { - super(); - criteria = new ArrayList(); - } - - public boolean isValid() { - return criteria.size() > 0; - } - - public List getAllCriteria() { - return criteria; - } - - public List getCriteria() { - return criteria; - } - - protected void addCriterion(String condition) { - if (condition == null) { - throw new RuntimeException("Value for condition cannot be null"); - } - criteria.add(new Criterion(condition)); - } - - protected void addCriterion(String condition, Object value, String property) { - if (value == null) { - throw new RuntimeException("Value for " + property + " cannot be null"); - } - criteria.add(new Criterion(condition, value)); - } - - protected void addCriterion(String condition, Object value1, Object value2, String property) { - if (value1 == null || value2 == null) { - throw new RuntimeException("Between values for " + property + " cannot be null"); - } - criteria.add(new Criterion(condition, value1, value2)); - } - - public Criteria andIdIsNull() { - addCriterion("ID is null"); - return (Criteria) this; - } - - public Criteria andIdIsNotNull() { - addCriterion("ID is not null"); - return (Criteria) this; - } - - public Criteria andIdEqualTo(Integer value) { - addCriterion("ID =", value, "id"); - return (Criteria) this; - } - - public Criteria andIdNotEqualTo(Integer value) { - addCriterion("ID <>", value, "id"); - return (Criteria) this; - } - - public Criteria andIdGreaterThan(Integer value) { - addCriterion("ID >", value, "id"); - return (Criteria) this; - } - - public Criteria andIdGreaterThanOrEqualTo(Integer value) { - addCriterion("ID >=", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLessThan(Integer value) { - addCriterion("ID <", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLessThanOrEqualTo(Integer value) { - addCriterion("ID <=", value, "id"); - return (Criteria) this; - } - - public Criteria andIdIn(List values) { - addCriterion("ID in", values, "id"); - return (Criteria) this; - } - - public Criteria andIdNotIn(List values) { - addCriterion("ID not in", values, "id"); - return (Criteria) this; - } - - public Criteria andIdBetween(Integer value1, Integer value2) { - addCriterion("ID between", value1, value2, "id"); - return (Criteria) this; - } - - public Criteria andIdNotBetween(Integer value1, Integer value2) { - addCriterion("ID not between", value1, value2, "id"); - return (Criteria) this; - } - - public Criteria andNameIsNull() { - addCriterion("Name is null"); - return (Criteria) this; - } - - public Criteria andNameIsNotNull() { - addCriterion("Name is not null"); - return (Criteria) this; - } - - public Criteria andNameEqualTo(String value) { - addCriterion("Name =", value, "name"); - return (Criteria) this; - } - - public Criteria andNameNotEqualTo(String value) { - addCriterion("Name <>", value, "name"); - return (Criteria) this; - } - - public Criteria andNameGreaterThan(String value) { - addCriterion("Name >", value, "name"); - return (Criteria) this; - } - - public Criteria andNameGreaterThanOrEqualTo(String value) { - addCriterion("Name >=", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLessThan(String value) { - addCriterion("Name <", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLessThanOrEqualTo(String value) { - addCriterion("Name <=", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLike(String value) { - addCriterion("Name like", value, "name"); - return (Criteria) this; - } - - public Criteria andNameNotLike(String value) { - addCriterion("Name not like", value, "name"); - return (Criteria) this; - } - - public Criteria andNameIn(List values) { - addCriterion("Name in", values, "name"); - return (Criteria) this; - } - - public Criteria andNameNotIn(List values) { - addCriterion("Name not in", values, "name"); - return (Criteria) this; - } - - public Criteria andNameBetween(String value1, String value2) { - addCriterion("Name between", value1, value2, "name"); - return (Criteria) this; - } - - public Criteria andNameNotBetween(String value1, String value2) { - addCriterion("Name not between", value1, value2, "name"); - return (Criteria) this; - } - - public Criteria andCountrycodeIsNull() { - addCriterion("CountryCode is null"); - return (Criteria) this; - } - - public Criteria andCountrycodeIsNotNull() { - addCriterion("CountryCode is not null"); - return (Criteria) this; - } - - public Criteria andCountrycodeEqualTo(String value) { - addCriterion("CountryCode =", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeNotEqualTo(String value) { - addCriterion("CountryCode <>", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeGreaterThan(String value) { - addCriterion("CountryCode >", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeGreaterThanOrEqualTo(String value) { - addCriterion("CountryCode >=", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeLessThan(String value) { - addCriterion("CountryCode <", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeLessThanOrEqualTo(String value) { - addCriterion("CountryCode <=", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeLike(String value) { - addCriterion("CountryCode like", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeNotLike(String value) { - addCriterion("CountryCode not like", value, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeIn(List values) { - addCriterion("CountryCode in", values, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeNotIn(List values) { - addCriterion("CountryCode not in", values, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeBetween(String value1, String value2) { - addCriterion("CountryCode between", value1, value2, "countrycode"); - return (Criteria) this; - } - - public Criteria andCountrycodeNotBetween(String value1, String value2) { - addCriterion("CountryCode not between", value1, value2, "countrycode"); - return (Criteria) this; - } - - public Criteria andDistrictIsNull() { - addCriterion("District is null"); - return (Criteria) this; - } - - public Criteria andDistrictIsNotNull() { - addCriterion("District is not null"); - return (Criteria) this; - } - - public Criteria andDistrictEqualTo(String value) { - addCriterion("District =", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictNotEqualTo(String value) { - addCriterion("District <>", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictGreaterThan(String value) { - addCriterion("District >", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictGreaterThanOrEqualTo(String value) { - addCriterion("District >=", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictLessThan(String value) { - addCriterion("District <", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictLessThanOrEqualTo(String value) { - addCriterion("District <=", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictLike(String value) { - addCriterion("District like", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictNotLike(String value) { - addCriterion("District not like", value, "district"); - return (Criteria) this; - } - - public Criteria andDistrictIn(List values) { - addCriterion("District in", values, "district"); - return (Criteria) this; - } - - public Criteria andDistrictNotIn(List values) { - addCriterion("District not in", values, "district"); - return (Criteria) this; - } - - public Criteria andDistrictBetween(String value1, String value2) { - addCriterion("District between", value1, value2, "district"); - return (Criteria) this; - } - - public Criteria andDistrictNotBetween(String value1, String value2) { - addCriterion("District not between", value1, value2, "district"); - return (Criteria) this; - } - - public Criteria andPopulationIsNull() { - addCriterion("Population is null"); - return (Criteria) this; - } - - public Criteria andPopulationIsNotNull() { - addCriterion("Population is not null"); - return (Criteria) this; - } - - public Criteria andPopulationEqualTo(Integer value) { - addCriterion("Population =", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationNotEqualTo(Integer value) { - addCriterion("Population <>", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationGreaterThan(Integer value) { - addCriterion("Population >", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationGreaterThanOrEqualTo(Integer value) { - addCriterion("Population >=", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationLessThan(Integer value) { - addCriterion("Population <", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationLessThanOrEqualTo(Integer value) { - addCriterion("Population <=", value, "population"); - return (Criteria) this; - } - - public Criteria andPopulationIn(List values) { - addCriterion("Population in", values, "population"); - return (Criteria) this; - } - - public Criteria andPopulationNotIn(List values) { - addCriterion("Population not in", values, "population"); - return (Criteria) this; - } - - public Criteria andPopulationBetween(Integer value1, Integer value2) { - addCriterion("Population between", value1, value2, "population"); - return (Criteria) this; - } - - public Criteria andPopulationNotBetween(Integer value1, Integer value2) { - addCriterion("Population not between", value1, value2, "population"); - return (Criteria) this; - } - } - - public static class Criteria extends GeneratedCriteria { - - protected Criteria() { - super(); - } - } - - public static class Criterion { - private String condition; - - private Object value; - - private Object secondValue; - - private boolean noValue; - - private boolean singleValue; - - private boolean betweenValue; - - private boolean listValue; - - private String typeHandler; - - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - - protected Criterion(String condition) { - super(); - this.condition = condition; - this.typeHandler = null; - this.noValue = true; - } - - protected Criterion(String condition, Object value, String typeHandler) { - super(); - this.condition = condition; - this.value = value; - this.typeHandler = typeHandler; - if (value instanceof List) { - this.listValue = true; - } else { - this.singleValue = true; - } - } - - protected Criterion(String condition, Object value) { - this(condition, value, null); - } - - protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { - super(); - this.condition = condition; - this.value = value; - this.secondValue = secondValue; - this.typeHandler = typeHandler; - this.betweenValue = true; - } - - protected Criterion(String condition, Object value, Object secondValue) { - this(condition, value, secondValue, null); - } - } -} \ No newline at end of file diff --git a/quick-modules/dao/src/main/java/com/modules/domain/City.java b/quick-modules/dao/src/main/java/com/modules/entity/City.java similarity index 97% rename from quick-modules/dao/src/main/java/com/modules/domain/City.java rename to quick-modules/dao/src/main/java/com/modules/entity/City.java index f4657330..baab8fea 100644 --- a/quick-modules/dao/src/main/java/com/modules/domain/City.java +++ b/quick-modules/dao/src/main/java/com/modules/entity/City.java @@ -1,4 +1,4 @@ -package com.modules.domain; +package com.modules.entity; public class City { private Integer id; diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/entity/Country.java b/quick-modules/dao/src/main/java/com/modules/entity/Country.java similarity index 97% rename from quick-mybatis-druid/src/main/java/com/quick/druid/entity/Country.java rename to quick-modules/dao/src/main/java/com/modules/entity/Country.java index 7c1f2593..e2e6ee77 100644 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/entity/Country.java +++ b/quick-modules/dao/src/main/java/com/modules/entity/Country.java @@ -1,7 +1,4 @@ -package com.quick.druid.entity; - -import org.apache.ibatis.annotations.Mapper; - +package com.modules.entity; public class Country { private String code; diff --git a/quick-modules/dao/src/main/resources/generatorConfig.xml b/quick-modules/dao/src/main/resources/generatorConfig.xml index ec5dae2d..a419e5ea 100644 --- a/quick-modules/dao/src/main/resources/generatorConfig.xml +++ b/quick-modules/dao/src/main/resources/generatorConfig.xml @@ -21,7 +21,7 @@ + password="root123"> @@ -29,7 +29,7 @@ - @@ -54,7 +54,9 @@ -
+
diff --git a/quick-modules/doc/WX20180810-230910@2x.png b/quick-modules/doc/WX20180810-230910@2x.png new file mode 100644 index 00000000..180bbf0e Binary files /dev/null and b/quick-modules/doc/WX20180810-230910@2x.png differ diff --git a/quick-modules/pom.xml b/quick-modules/pom.xml index 8ccd134b..802dea68 100644 --- a/quick-modules/pom.xml +++ b/quick-modules/pom.xml @@ -4,7 +4,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> quick-modules - com.quick pom 1.0-SNAPSHOT @@ -14,22 +13,71 @@ dao service1 service2 + test - - 1.8 - 1.2.0 - 5.1.39 - - org.springframework.boot - spring-boot-starter-parent - 1.5.1.RELEASE - + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + nexus + Releases + http://nexus.wangxc.club:8081/repository/maven-releases + + + nexus + Snapshot + http://nexus.wangxc.club:8081/repository/maven-snapshots + + + + + + + nexus + Nexus Plugin Repository + http://nexus.wangxc.club:8081/repository/maven-public/ + + true + + + true + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + true + + + + attach-sources + + package + + jar-no-fork + + + + + + + \ No newline at end of file diff --git a/quick-modules/service1/pom.xml b/quick-modules/service1/pom.xml index 150db149..059535e3 100644 --- a/quick-modules/service1/pom.xml +++ b/quick-modules/service1/pom.xml @@ -14,75 +14,28 @@ - - com.quick - quick-modules-dao - 1.0-SNAPSHOT - - org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-test - test + com.quick + quick-modules-dao + 1.0-SNAPSHOT - org.mybatis.spring.boot mybatis-spring-boot-starter - ${mybatis-spring-boot} - + - mysql - mysql-connector-java - ${mysql-connector} + com.alibaba + fastjson - - - - junit - junit - 4.12 - - - org.apache.commons - commons-pool2 - 2.4.2 - - - commons-beanutils - commons-beanutils - 1.9.2 - - - - commons-logging - commons-logging - 1.2 - - - - commons-dbcp - commons-dbcp - 1.4 - - - - net.sf.json-lib - json-lib - 2.4 - jdk15 - - diff --git a/quick-modules/service1/src/main/java/com/modules/Application.java b/quick-modules/service1/src/main/java/com/modules/Application.java index c0790c2d..565b2061 100644 --- a/quick-modules/service1/src/main/java/com/modules/Application.java +++ b/quick-modules/service1/src/main/java/com/modules/Application.java @@ -1,9 +1,8 @@ package com.modules; +import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.scheduling.annotation.EnableScheduling; /** * Created with IDEA @@ -12,10 +11,9 @@ * Time: 8:42 */ @SpringBootApplication -@EnableScheduling -@EnableAutoConfiguration -public class Application { - public static void main(String[] args) { - SpringApplication.run(Application.class); - } +@MapperScan("com.modules.dao") +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class); + } } diff --git a/quick-modules/service1/src/main/java/com/modules/api/APIController.java b/quick-modules/service1/src/main/java/com/modules/api/APIController.java index 866787e2..dae5d3e9 100644 --- a/quick-modules/service1/src/main/java/com/modules/api/APIController.java +++ b/quick-modules/service1/src/main/java/com/modules/api/APIController.java @@ -1,9 +1,8 @@ package com.modules.api; -import com.modules.domain.City; +import com.modules.entity.City; import com.modules.service.CityService; -import net.sf.json.JSONObject; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -24,15 +23,8 @@ public class APIController { private CityService cityService; @RequestMapping("/hello/{id}") - public JSONObject getCity(@PathVariable(value = "id",required = false)Integer id){ - JSONObject result = new JSONObject(); - if(id==null){ - result.accumulate("errMessage","请填写参数"); - return result; - } - City city = cityService.selectById(id); - result.accumulate("data",city); - return result; + public City getCity(@PathVariable(value = "id",required = false)Integer id){ + return cityService.selectById(id); } } diff --git a/quick-modules/service1/src/main/java/com/modules/service/CityService.java b/quick-modules/service1/src/main/java/com/modules/service/CityService.java index 43c97bf3..81bbfc19 100644 --- a/quick-modules/service1/src/main/java/com/modules/service/CityService.java +++ b/quick-modules/service1/src/main/java/com/modules/service/CityService.java @@ -1,6 +1,6 @@ package com.modules.service; -import com.modules.domain.City; +import com.modules.entity.City; /** * Created with IDEA diff --git a/quick-modules/service2/src/main/java/com/modules/serviceimpl/CityServiceImpl.java b/quick-modules/service1/src/main/java/com/modules/service/impl/CityServiceImpl.java similarity index 59% rename from quick-modules/service2/src/main/java/com/modules/serviceimpl/CityServiceImpl.java rename to quick-modules/service1/src/main/java/com/modules/service/impl/CityServiceImpl.java index 207d7fed..4b663acb 100644 --- a/quick-modules/service2/src/main/java/com/modules/serviceimpl/CityServiceImpl.java +++ b/quick-modules/service1/src/main/java/com/modules/service/impl/CityServiceImpl.java @@ -1,7 +1,7 @@ -package com.modules.serviceimpl; +package com.modules.service.impl; import com.modules.dao.CityMapper; -import com.modules.domain.City; +import com.modules.entity.City; import com.modules.service.CityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -15,11 +15,11 @@ @Service public class CityServiceImpl implements CityService { - @Autowired - private CityMapper cityMapper; + @Autowired + private CityMapper cityMapper; - @Override - public City selectById(int id) { - return cityMapper.selectByPrimaryKey(1); - } + @Override + public City selectById(int id) { + return cityMapper.selectByPrimaryKey(id); + } } diff --git a/quick-modules/service1/src/main/java/com/modules/serviceimpl/CityServiceImpl.java b/quick-modules/service1/src/main/java/com/modules/serviceimpl/CityServiceImpl.java deleted file mode 100644 index 207d7fed..00000000 --- a/quick-modules/service1/src/main/java/com/modules/serviceimpl/CityServiceImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.modules.serviceimpl; - -import com.modules.dao.CityMapper; -import com.modules.domain.City; -import com.modules.service.CityService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * Created with IDEA - * User: vector - * Data: 2017/4/1 - * Time: 8:38 - */ -@Service -public class CityServiceImpl implements CityService { - - @Autowired - private CityMapper cityMapper; - - @Override - public City selectById(int id) { - return cityMapper.selectByPrimaryKey(1); - } -} diff --git a/quick-modules/service1/src/main/resources/application.properties b/quick-modules/service1/src/main/resources/application.properties index dfd92efa..c938f89a 100644 --- a/quick-modules/service1/src/main/resources/application.properties +++ b/quick-modules/service1/src/main/resources/application.properties @@ -1,14 +1,12 @@ server.port=8001 -logging.level.root=error +logging.level.root=info spring.datasource.driver-class-name=com.mysql.jdbc.Driver -###内rm-uf6368hl62p88mhq0.mysql.rds.aliyuncs.com -###外rm-uf6368hl62p88mhq0o.mysql.rds.aliyuncs.com -spring.datasource.url=jdbc:mysql://localhost:3306/world?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull +spring.datasource.url=jdbc:mysql://localhost:3306/world?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false spring.datasource.username=root -spring.datasource.password=root123 +spring.datasource.password=123456 # 下面为连接池的补充设置,应用到上面所有数据源中 @@ -30,6 +28,3 @@ spring.datasource.testOnReturn=false spring.datasource.poolPreparedStatements=true spring.datasource.maxPoolPreparedStatementPerConnectionSize=20 -## Mybatis 配置 -mybatis.typeAliasesPackage=com.modules.domain -mybatis.mapperLocations=classpath:mapper/*.xml diff --git a/quick-modules/service2/pom.xml b/quick-modules/service2/pom.xml index f017fc76..2ec1c7dd 100644 --- a/quick-modules/service2/pom.xml +++ b/quick-modules/service2/pom.xml @@ -25,61 +25,10 @@ spring-boot-starter-web
- - - org.springframework.boot - spring-boot-starter-test - test - - org.mybatis.spring.boot mybatis-spring-boot-starter - ${mybatis-spring-boot} - - - - - mysql - mysql-connector-java - ${mysql-connector} - - - - - junit - junit - 4.12 - - - org.apache.commons - commons-pool2 - 2.4.2 - - - commons-beanutils - commons-beanutils - 1.9.2 - - - - commons-logging - commons-logging - 1.2 - - - - commons-dbcp - commons-dbcp - 1.4 - - - - net.sf.json-lib - json-lib - 2.4 - jdk15
diff --git a/quick-modules/service2/src/main/java/com/modules/Application.java b/quick-modules/service2/src/main/java/com/modules/Application.java index 8af7b917..565b2061 100644 --- a/quick-modules/service2/src/main/java/com/modules/Application.java +++ b/quick-modules/service2/src/main/java/com/modules/Application.java @@ -1,9 +1,8 @@ package com.modules; +import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.scheduling.annotation.EnableScheduling; /** * Created with IDEA @@ -12,10 +11,9 @@ * Time: 8:42 */ @SpringBootApplication -@EnableScheduling -@EnableAutoConfiguration +@MapperScan("com.modules.dao") public class Application { - public static void main(String[] args) { - SpringApplication.run(Application.class); - } + public static void main(String[] args) { + SpringApplication.run(Application.class); + } } diff --git a/quick-modules/service2/src/main/java/com/modules/api/APIController.java b/quick-modules/service2/src/main/java/com/modules/api/APIController.java index 802c3b42..2e9e992f 100644 --- a/quick-modules/service2/src/main/java/com/modules/api/APIController.java +++ b/quick-modules/service2/src/main/java/com/modules/api/APIController.java @@ -1,7 +1,8 @@ package com.modules.api; -import com.modules.service.CityService; -import net.sf.json.JSONObject; +import com.modules.entity.Country; +import com.modules.service.CountryService; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -17,14 +18,12 @@ @RequestMapping("/service2") public class APIController { - @Resource - private CityService cityService; + @Resource + private CountryService countryService; - @RequestMapping("/hello") - public JSONObject hello(){ - JSONObject result = new JSONObject(); - result.accumulate("data","this is service2"); - return result; - } + @RequestMapping("/hello/{code}") + public Country hello(@PathVariable("code") String code) { + return countryService.selectByCode(code); + } } diff --git a/quick-modules/service2/src/main/java/com/modules/service/CityService.java b/quick-modules/service2/src/main/java/com/modules/service/CountryService.java similarity index 50% rename from quick-modules/service2/src/main/java/com/modules/service/CityService.java rename to quick-modules/service2/src/main/java/com/modules/service/CountryService.java index 43c97bf3..0b3a1732 100644 --- a/quick-modules/service2/src/main/java/com/modules/service/CityService.java +++ b/quick-modules/service2/src/main/java/com/modules/service/CountryService.java @@ -1,6 +1,6 @@ package com.modules.service; -import com.modules.domain.City; +import com.modules.entity.Country; /** * Created with IDEA @@ -8,6 +8,6 @@ * Data: 2017/4/1 * Time: 8:38 */ -public interface CityService { - City selectById(int id); +public interface CountryService { + Country selectByCode(String code); } diff --git a/quick-modules/service2/src/main/java/com/modules/service/impl/CountryServiceImpl.java b/quick-modules/service2/src/main/java/com/modules/service/impl/CountryServiceImpl.java new file mode 100644 index 00000000..83cd0133 --- /dev/null +++ b/quick-modules/service2/src/main/java/com/modules/service/impl/CountryServiceImpl.java @@ -0,0 +1,24 @@ +package com.modules.service.impl; + +import com.modules.dao.CountryMapper; +import com.modules.entity.Country; +import com.modules.service.CountryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author vector + * @Data 2018/8/9 0009 + * @Description TODO + */ +@Service +public class CountryServiceImpl implements CountryService { + + @Autowired + private CountryMapper countryMapper; + + @Override + public Country selectByCode(String code) { + return countryMapper.selectByPrimaryKey(code); + } +} diff --git a/quick-modules/service2/src/main/resources/application.properties b/quick-modules/service2/src/main/resources/application.properties index 3445ca68..8fff6a08 100644 --- a/quick-modules/service2/src/main/resources/application.properties +++ b/quick-modules/service2/src/main/resources/application.properties @@ -1,12 +1,10 @@ server.port=8002 -logging.level.root=error +logging.level.root=info spring.datasource.driver-class-name=com.mysql.jdbc.Driver -###内rm-uf6368hl62p88mhq0.mysql.rds.aliyuncs.com -###外rm-uf6368hl62p88mhq0o.mysql.rds.aliyuncs.com -spring.datasource.url=jdbc:mysql://localhost:3306/world?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull +spring.datasource.url=jdbc:mysql://localhost:3306/world?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false spring.datasource.username=root spring.datasource.password=root123 @@ -29,7 +27,3 @@ spring.datasource.testOnReturn=false # 打开PSCache,并且指定每个连接上PSCache的大小 spring.datasource.poolPreparedStatements=true spring.datasource.maxPoolPreparedStatementPerConnectionSize=20 - -## Mybatis 配置 -mybatis.typeAliasesPackage=com.modules.domain -mybatis.mapperLocations=classpath:mapper/*.xml diff --git a/quick-modules/settings.xml b/quick-modules/settings.xml new file mode 100644 index 00000000..9c26bd18 --- /dev/null +++ b/quick-modules/settings.xml @@ -0,0 +1,156 @@ + + + + + + /Users/wangxc/Develop/maven/repo + + + + + + + + + + + + + nexus + admin + admin123 + + + + + + nexus + * + http://nexus.wangxc.club/repository/maven-public/ + + + + + + nexus + + + + + central + http://central + + true + + + true + + + + + + central + http://central + + true + + + true + + + + + + + + nexus + + \ No newline at end of file diff --git a/quick-modules/test/pom.xml b/quick-modules/test/pom.xml new file mode 100644 index 00000000..d00a1aca --- /dev/null +++ b/quick-modules/test/pom.xml @@ -0,0 +1,64 @@ + + + test + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../../quick-platform/pom.xml + + + + org.springframework.boot + spring-boot-starter-web + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 1.1.1 + + + org.springframework.boot + spring-boot-starter-test + + + com.quick + quick-modules-dao + 1.0-SNAPSHOT + + + com.alibaba + fastjson + + + + + + nexus + Nexus Repository + http://nexus.wangxc.club:8081/repository/maven-public/ + + true + + + true + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 1.5.4.RELEASE + + + + + \ No newline at end of file diff --git a/quick-modules/test/src/main/java/com/multi/test/Application.java b/quick-modules/test/src/main/java/com/multi/test/Application.java new file mode 100644 index 00000000..fad29f44 --- /dev/null +++ b/quick-modules/test/src/main/java/com/multi/test/Application.java @@ -0,0 +1,18 @@ +package com.multi.test; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author vector + * @Data 2018/8/10 0010 + * @Description TODO + */ +@SpringBootApplication +@MapperScan("com.modules.dao") +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/quick-modules/test/src/main/java/com/multi/test/service/CityService.java b/quick-modules/test/src/main/java/com/multi/test/service/CityService.java new file mode 100644 index 00000000..ba678e43 --- /dev/null +++ b/quick-modules/test/src/main/java/com/multi/test/service/CityService.java @@ -0,0 +1,12 @@ +package com.multi.test.service; + +import com.modules.entity.City; + +/** + * @author vector + * @Data 2018/8/10 0010 + * @Description TODO + */ +public interface CityService { + City getCity(int id); +} diff --git a/quick-modules/test/src/main/java/com/multi/test/service/impl/CityServiceImpl.java b/quick-modules/test/src/main/java/com/multi/test/service/impl/CityServiceImpl.java new file mode 100644 index 00000000..642b1b84 --- /dev/null +++ b/quick-modules/test/src/main/java/com/multi/test/service/impl/CityServiceImpl.java @@ -0,0 +1,25 @@ +package com.multi.test.service.impl; + +import com.modules.dao.CityMapper; +import com.modules.entity.City; +import com.multi.test.service.CityService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author vector + * @Data 2018/8/10 0010 + * @Description TODO + */ +@Service +public class CityServiceImpl implements CityService { + + @Autowired + private CityMapper cityMapper; + + @Override + public City getCity(int id) { + return cityMapper.selectByPrimaryKey(id + ); + } +} diff --git a/quick-modules/test/src/main/resources/application.properties b/quick-modules/test/src/main/resources/application.properties new file mode 100644 index 00000000..c938f89a --- /dev/null +++ b/quick-modules/test/src/main/resources/application.properties @@ -0,0 +1,30 @@ +server.port=8001 + + +logging.level.root=info + +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/world?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false +spring.datasource.username=root +spring.datasource.password=123456 + + +# 下面为连接池的补充设置,应用到上面所有数据源中 +# 初始化大小,最小,最大 +spring.datasource.initialSize=5 +spring.datasource.minIdle=5 +spring.datasource.maxActive=20 +# 配置获取连接等待超时的时间 +spring.datasource.maxWait=60000 +# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 +spring.datasource.timeBetweenEvictionRunsMillis=60000 +# 配置一个连接在池中最小生存的时间,单位是毫秒 +spring.datasource.minEvictableIdleTimeMillis=300000 +spring.datasource.validationQuery=SELECT 1 FROM DUAL +spring.datasource.testWhileIdle=true +spring.datasource.testOnBorrow=false +spring.datasource.testOnReturn=false +# 打开PSCache,并且指定每个连接上PSCache的大小 +spring.datasource.poolPreparedStatements=true +spring.datasource.maxPoolPreparedStatementPerConnectionSize=20 + diff --git a/quick-modules/test/src/test/java/CityServiceTest.java b/quick-modules/test/src/test/java/CityServiceTest.java new file mode 100644 index 00000000..1dcd8635 --- /dev/null +++ b/quick-modules/test/src/test/java/CityServiceTest.java @@ -0,0 +1,30 @@ +import com.alibaba.fastjson.JSON; +import com.multi.test.Application; +import com.multi.test.service.CityService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; + +/** + * @author vector + * @Data 2018/8/10 0010 + * @Description + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class) +public class CityServiceTest { + + @Resource + private CityService cityService; + + @Test + public void testGetCity() { + System.out.println(JSON.toJSONString(cityService.getCity(1))); + /** + * {"countrycode":"AFG","district":"Kabol","id":1,"name":"Kabul","population":1780000} + */ + } +} diff --git a/quick-mongodb/README.md b/quick-mongodb/README.md new file mode 100644 index 00000000..bb01e632 --- /dev/null +++ b/quick-mongodb/README.md @@ -0,0 +1,9 @@ + +参考: +- https://spring.io/guides/gs/accessing-data-mongodb/ +- https://github.com/spring-guides/gs-accessing-data-mongodb/blob/master/complete/src/main/java/com/example/accessingdatamongodb/AccessingDataMongodbApplication.java#L24-L31 + + + +注意uri的写法和密码加密的方式 +`uri: mongodb://admin:Www1hirede@192.168.1.35:27017/test-springboot?authSource=admin&authMechanism=SCRAM-SHA-1` \ No newline at end of file diff --git a/quick-mongodb/pom.xml b/quick-mongodb/pom.xml new file mode 100644 index 00000000..e92287c6 --- /dev/null +++ b/quick-mongodb/pom.xml @@ -0,0 +1,50 @@ + + + quick-mongodb + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.1.6.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/quick-mongodb/src/main/java/com/quick/mongodb/MongoApplication.java b/quick-mongodb/src/main/java/com/quick/mongodb/MongoApplication.java new file mode 100644 index 00000000..6e99c84c --- /dev/null +++ b/quick-mongodb/src/main/java/com/quick/mongodb/MongoApplication.java @@ -0,0 +1,51 @@ +package com.quick.mongodb; + +import com.quick.mongodb.entity.Customer; +import com.quick.mongodb.repo.CustomerRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author vector + * @date: 2019/9/29 0029 15:13 + */ +@SpringBootApplication +public class MongoApplication implements CommandLineRunner { + + @Autowired + private CustomerRepository customerRepository; + + public static void main(String[] args) { + SpringApplication.run(MongoApplication.class); + } + + @Override + public void run(String... args) throws Exception { + customerRepository.deleteAll(); + + customerRepository.save(new Customer("vector", "wang")); + customerRepository.save(new Customer("bmhjqs", "wang")); + + System.out.println("customer fetch with findAll()"); + System.out.println("-----------------------------"); + for (Customer customer : customerRepository.findAll()) { + System.out.println(customer); + } + System.out.println(); + + System.out.println("Customer found with findByFirstName('vector'):"); + System.out.println("--------------------------------"); + System.out.println(customerRepository.findByFirstName("vector")); + + + System.out.println("Customers found with findByLastName('wang'):"); + System.out.println("--------------------------------"); + for (Customer customer : customerRepository.findByLastName("wang")) { + System.out.println(customer); + } + + + } +} diff --git a/quick-mongodb/src/main/java/com/quick/mongodb/entity/Customer.java b/quick-mongodb/src/main/java/com/quick/mongodb/entity/Customer.java new file mode 100644 index 00000000..9d67204e --- /dev/null +++ b/quick-mongodb/src/main/java/com/quick/mongodb/entity/Customer.java @@ -0,0 +1,34 @@ +package com.quick.mongodb.entity; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * @author vector + * @date: 2019/9/29 0029 15:05 + */ +@Document(collection="customer") +public class Customer { + + @Id + public String id; + + public String firstName; + public String lastName; + + public Customer() { + } + + public Customer(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + @Override + public String toString() { + return String.format( + "Customer[id=%s, firstName='%s', lastName='%s']", + id, firstName, lastName); + } + +} diff --git a/quick-mongodb/src/main/java/com/quick/mongodb/repo/CustomerRepository.java b/quick-mongodb/src/main/java/com/quick/mongodb/repo/CustomerRepository.java new file mode 100644 index 00000000..07417628 --- /dev/null +++ b/quick-mongodb/src/main/java/com/quick/mongodb/repo/CustomerRepository.java @@ -0,0 +1,17 @@ +package com.quick.mongodb.repo; + +import com.quick.mongodb.entity.Customer; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +/** + * @author vector + * @date: 2019/9/29 0029 15:08 + */ +public interface CustomerRepository extends MongoRepository { + + public Customer findByFirstName(String firstName); + + public List findByLastName(String lastName); +} diff --git a/quick-mongodb/src/main/resources/application.yml b/quick-mongodb/src/main/resources/application.yml new file mode 100644 index 00000000..ed65c777 --- /dev/null +++ b/quick-mongodb/src/main/resources/application.yml @@ -0,0 +1,4 @@ +spring: + data: + mongodb: + uri: mongodb://admin:Www1hirede@192.168.1.35:27017/test-springboot?authSource=admin&authMechanism=SCRAM-SHA-1 \ No newline at end of file diff --git a/quick-monitor-thread/pom.xml b/quick-monitor-thread/pom.xml new file mode 100644 index 00000000..33c2f4d2 --- /dev/null +++ b/quick-monitor-thread/pom.xml @@ -0,0 +1,36 @@ + + + quick-monitor-thread + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + UTF-8 + UTF-8 + 1.8 + murraco.JwtAuthServiceApp + + + + org.springframework.boot + spring-boot-starter-parent + 1.5.6.RELEASE + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + + \ No newline at end of file diff --git a/quick-monitor-thread/src/main/java/com/monitor/thread/Application.java b/quick-monitor-thread/src/main/java/com/monitor/thread/Application.java new file mode 100644 index 00000000..1e263862 --- /dev/null +++ b/quick-monitor-thread/src/main/java/com/monitor/thread/Application.java @@ -0,0 +1,17 @@ +package com.monitor.thread; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2019/3/7 下午11:11 + * + */ +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/quick-monitor-thread/src/main/java/com/monitor/thread/config/ApplicationContextProvider.java b/quick-monitor-thread/src/main/java/com/monitor/thread/config/ApplicationContextProvider.java new file mode 100644 index 00000000..2707d5d6 --- /dev/null +++ b/quick-monitor-thread/src/main/java/com/monitor/thread/config/ApplicationContextProvider.java @@ -0,0 +1,25 @@ +package com.monitor.thread.config; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class ApplicationContextProvider implements ApplicationContextAware { + + private static ApplicationContext context; + + private ApplicationContextProvider(){} + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + context = applicationContext; + } + + public static T getBean(String name,Class aClass){ + return context.getBean(name,aClass); + } + + +} \ No newline at end of file diff --git a/quick-monitor-thread/src/main/java/com/monitor/thread/controller/ApiController.java b/quick-monitor-thread/src/main/java/com/monitor/thread/controller/ApiController.java new file mode 100644 index 00000000..7bbfeb5e --- /dev/null +++ b/quick-monitor-thread/src/main/java/com/monitor/thread/controller/ApiController.java @@ -0,0 +1,20 @@ +package com.monitor.thread.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * @author wangxc + * @date: 2019/3/7 下午11:15 + * + */ +@RestController +@RequestMapping("/") +public class ApiController { + @RequestMapping(value = "/test", method = RequestMethod.GET) + public String test() { + return "Hello there"; + } +} diff --git a/quick-monitor-thread/src/main/java/com/monitor/thread/service/SpecialService.java b/quick-monitor-thread/src/main/java/com/monitor/thread/service/SpecialService.java new file mode 100644 index 00000000..7498bdf0 --- /dev/null +++ b/quick-monitor-thread/src/main/java/com/monitor/thread/service/SpecialService.java @@ -0,0 +1,25 @@ +package com.monitor.thread.service; + +import org.springframework.stereotype.Service; + +import javax.xml.ws.ServiceMode; + +/** + * + * @author wangxc + * @date: 2019/3/7 下午11:14 + * + */ +@Service +public class SpecialService { + + public void build() { + try { + System.out.println(Thread.currentThread().getName() + " ====>异步执行中。。。"); + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } +} diff --git a/quick-monitor-thread/src/main/java/com/monitor/thread/task/MoniotrTask.java b/quick-monitor-thread/src/main/java/com/monitor/thread/task/MoniotrTask.java new file mode 100644 index 00000000..644754ad --- /dev/null +++ b/quick-monitor-thread/src/main/java/com/monitor/thread/task/MoniotrTask.java @@ -0,0 +1,40 @@ +package com.monitor.thread.task; + +import com.monitor.thread.service.SpecialService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.management.monitor.Monitor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +@Component("mTask") +//@Scope("prototype")//非单例 +public class MoniotrTask extends Thread { + + final static Logger logger = LoggerFactory.getLogger(MoniotrTask.class); + + //参数封装 + private Monitor monitor; + + public void setMonitor(Monitor monitor) { + this.monitor = monitor; + } + + @Resource + private SpecialService specialService; + + public SpecialService getSpecialService() { + return specialService; + } + + @Override + public void run() { + logger.info("线程:" + Thread.currentThread().getName() + "运行中....."); + specialService.build(); + } + +} \ No newline at end of file diff --git a/quick-monitor-thread/src/main/java/com/monitor/thread/task/StartTask.java b/quick-monitor-thread/src/main/java/com/monitor/thread/task/StartTask.java new file mode 100644 index 00000000..7f38d5c8 --- /dev/null +++ b/quick-monitor-thread/src/main/java/com/monitor/thread/task/StartTask.java @@ -0,0 +1,29 @@ +package com.monitor.thread.task; + +import com.monitor.thread.config.ApplicationContextProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +@Component +@Order(value = 999999) +public class StartTask { + + final static Logger logger = LoggerFactory.getLogger(StartTask.class); + + + + //定义在构造方法完毕后,执行这个初始化方法 + @PostConstruct + public void init() { + MoniotrTask moniotrTask = ApplicationContextProvider.getBean("mTask", MoniotrTask.class); + moniotrTask.start(); + } + + +} \ No newline at end of file diff --git a/quick-monitor-thread/src/test/java/task/SpingContextTest.java b/quick-monitor-thread/src/test/java/task/SpingContextTest.java new file mode 100644 index 00000000..ed58b334 --- /dev/null +++ b/quick-monitor-thread/src/test/java/task/SpingContextTest.java @@ -0,0 +1,29 @@ +package task; + +import com.monitor.thread.Application; +import com.monitor.thread.config.ApplicationContextProvider; +import com.monitor.thread.task.MoniotrTask; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes =Application.class) +public class SpingContextTest { + + + + @Test + public void show()throws Exception{ + MoniotrTask m1= ApplicationContextProvider.getBean("mTask", MoniotrTask.class); + MoniotrTask m2=ApplicationContextProvider.getBean("mTask", MoniotrTask.class); + MoniotrTask m3=ApplicationContextProvider.getBean("mTask", MoniotrTask.class); + System.out.println(m1+" => "+m1.getSpecialService()); + System.out.println(m2+" => "+m2.getSpecialService()); + System.out.println(m3+" => "+m3.getSpecialService()); + + } + + +} \ No newline at end of file diff --git a/quick-multi-api-invoker/pom.xml b/quick-multi-api-invoker/pom.xml new file mode 100644 index 00000000..ecc7cd30 --- /dev/null +++ b/quick-multi-api-invoker/pom.xml @@ -0,0 +1,31 @@ + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + 4.0.0 + + quick-multi-api-invoker + jar + + quick-multi-api-invoker + http://maven.apache.org + + + UTF-8 + + + + + org.springframework.boot + spring-boot-starter + + + org.projectlombok + lombok + + + diff --git a/quick-multi-api-invoker/src/main/java/com/quick/ApiInvokerApp.java b/quick-multi-api-invoker/src/main/java/com/quick/ApiInvokerApp.java new file mode 100644 index 00000000..1ff4640c --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/ApiInvokerApp.java @@ -0,0 +1,14 @@ +package com.quick; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Hello world! + */ +@SpringBootApplication +public class ApiInvokerApp { + public static void main(String[] args) { + SpringApplication.run(ApiInvokerApp.class, args); + } +} diff --git a/quick-multi-api-invoker/src/main/java/com/quick/config/ApiInvokerFactory.java b/quick-multi-api-invoker/src/main/java/com/quick/config/ApiInvokerFactory.java new file mode 100644 index 00000000..0c6bfc5f --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/config/ApiInvokerFactory.java @@ -0,0 +1,39 @@ +package com.quick.config; + +import com.quick.enums.ApiInvokerType; +import com.quick.service.base.AbstractApiInvoker; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.EnumMap; +import java.util.Map; + +@Component +@Slf4j +public class ApiInvokerFactory implements InitializingBean, ApplicationContextAware { + + private static final Map INVOKER_MAP = new EnumMap<>(ApiInvokerType.class); + private ApplicationContext appContext; + + public AbstractApiInvoker getInvoker(ApiInvokerType invokerEnum) { + return INVOKER_MAP.get(invokerEnum); + } + + @Override + public void afterPropertiesSet() throws Exception { + appContext.getBeansOfType(AbstractApiInvoker.class) + .values() + .forEach(invoker -> INVOKER_MAP.put(invoker.getInvokerType(), invoker)); + + log.info("init INVOKER_MAP: {}",INVOKER_MAP); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + appContext = applicationContext; + } +} \ No newline at end of file diff --git a/quick-multi-api-invoker/src/main/java/com/quick/dto/BaseRequestBody.java b/quick-multi-api-invoker/src/main/java/com/quick/dto/BaseRequestBody.java new file mode 100644 index 00000000..94392178 --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/dto/BaseRequestBody.java @@ -0,0 +1,9 @@ +package com.quick.dto; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class BaseRequestBody implements Serializable { +} diff --git a/quick-multi-api-invoker/src/main/java/com/quick/dto/BaseRequestParam.java b/quick-multi-api-invoker/src/main/java/com/quick/dto/BaseRequestParam.java new file mode 100644 index 00000000..a9565e96 --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/dto/BaseRequestParam.java @@ -0,0 +1,9 @@ +package com.quick.dto; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class BaseRequestParam implements Serializable { +} diff --git a/quick-multi-api-invoker/src/main/java/com/quick/dto/BaseResponseBody.java b/quick-multi-api-invoker/src/main/java/com/quick/dto/BaseResponseBody.java new file mode 100644 index 00000000..38f1337b --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/dto/BaseResponseBody.java @@ -0,0 +1,9 @@ +package com.quick.dto; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class BaseResponseBody implements Serializable { +} diff --git a/quick-multi-api-invoker/src/main/java/com/quick/enums/ApiInvokerType.java b/quick-multi-api-invoker/src/main/java/com/quick/enums/ApiInvokerType.java new file mode 100644 index 00000000..f98e2483 --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/enums/ApiInvokerType.java @@ -0,0 +1,33 @@ +package com.quick.enums; + +public enum ApiInvokerType { + + SCENE_A("SCENE_A"), + SCENE_B("SCENE_B"), + SCENE_C("SCENE_C"), + SCENE_D("SCENE_D"); + + + ApiInvokerType(String type) { + this.type = type; + } + public static ApiInvokerType getTypeEnum(String code){ + ApiInvokerType[] values = ApiInvokerType.values(); + for (ApiInvokerType typeEnum :values){ + if(typeEnum.type.equals(code)){ + return typeEnum; + } + } + return null; + } + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/quick-multi-api-invoker/src/main/java/com/quick/service/ApiService.java b/quick-multi-api-invoker/src/main/java/com/quick/service/ApiService.java new file mode 100644 index 00000000..f34b769b --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/service/ApiService.java @@ -0,0 +1,4 @@ +package com.quick.service; + +public interface ApiService { +} diff --git a/quick-multi-api-invoker/src/main/java/com/quick/service/base/AbstractApiInvoker.java b/quick-multi-api-invoker/src/main/java/com/quick/service/base/AbstractApiInvoker.java new file mode 100644 index 00000000..4f86a6e1 --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/service/base/AbstractApiInvoker.java @@ -0,0 +1,6 @@ +package com.quick.service.base; + +public abstract class AbstractApiInvoker implements ApiInvoker, ApiVerify { + + +} diff --git a/quick-multi-api-invoker/src/main/java/com/quick/service/base/ApiInvoker.java b/quick-multi-api-invoker/src/main/java/com/quick/service/base/ApiInvoker.java new file mode 100644 index 00000000..5193b03e --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/service/base/ApiInvoker.java @@ -0,0 +1,42 @@ +package com.quick.service.base; + +import com.quick.dto.BaseRequestBody; +import com.quick.dto.BaseRequestParam; +import com.quick.dto.BaseResponseBody; +import com.quick.enums.ApiInvokerType; + +public interface ApiInvoker { + + /** + * 需指定调用者类型,区分不同厂商或者同一厂商不同的api + * + * @return + */ + ApiInvokerType getInvokerType(); + + + /** + * 构建请求体 + * @return + * @param + */ + T buildRequestBody(E e); + + + /** + * 获取响应体,基本上直接获取目标内容 + * @return + * @param + */ + T buildResponseBody(String response); + + /** + * 获取当前的调用url + * @return + */ + String getApiUrl(); + + + + +} diff --git a/quick-multi-api-invoker/src/main/java/com/quick/service/base/ApiVerify.java b/quick-multi-api-invoker/src/main/java/com/quick/service/base/ApiVerify.java new file mode 100644 index 00000000..282f2d9d --- /dev/null +++ b/quick-multi-api-invoker/src/main/java/com/quick/service/base/ApiVerify.java @@ -0,0 +1,5 @@ +package com.quick.service.base; + +public interface ApiVerify { + void verify() throws Exception; +} diff --git a/quick-multi-api-invoker/src/test/java/com/quick/AppTest.java b/quick-multi-api-invoker/src/test/java/com/quick/AppTest.java new file mode 100644 index 00000000..eb9df650 --- /dev/null +++ b/quick-multi-api-invoker/src/test/java/com/quick/AppTest.java @@ -0,0 +1,38 @@ +package com.quick; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/quick-multi-data/pom.xml b/quick-multi-data/pom.xml index bb34b071..a583ab49 100644 --- a/quick-multi-data/pom.xml +++ b/quick-multi-data/pom.xml @@ -9,17 +9,16 @@ - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml UTF-8 UTF-8 1.8 - 5.1.30 @@ -62,13 +61,11 @@ org.mybatis.spring.boot mybatis-spring-boot-starter - 1.1.1 mysql mysql-connector-java - ${mysql.version} diff --git a/quick-multi-data/src/main/java/com/quick/mulit/Application.java b/quick-multi-data/src/main/java/com/quick/mulit/Application.java index 3428d5d0..fa4a0258 100644 --- a/quick-multi-data/src/main/java/com/quick/mulit/Application.java +++ b/quick-multi-data/src/main/java/com/quick/mulit/Application.java @@ -2,6 +2,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.transaction.annotation.EnableTransactionManagement; /** * @Author: wangxc @@ -11,6 +13,8 @@ * @wxid: BMHJQS */ @SpringBootApplication +@ServletComponentScan +@EnableTransactionManagement public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); diff --git a/quick-multi-data/src/main/java/com/quick/mulit/config/DataSourcePrimaryConfig.java b/quick-multi-data/src/main/java/com/quick/mulit/config/DataSourcePrimaryConfig.java index 598b8d24..3b97928c 100644 --- a/quick-multi-data/src/main/java/com/quick/mulit/config/DataSourcePrimaryConfig.java +++ b/quick-multi-data/src/main/java/com/quick/mulit/config/DataSourcePrimaryConfig.java @@ -1,12 +1,13 @@ package com.quick.mulit.config; +import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -14,6 +15,7 @@ import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; +import java.sql.SQLException; /** * Created with IDEA @@ -26,11 +28,31 @@ @MapperScan(basePackages = "com.quick.mulit.mapper.primary", sqlSessionTemplateRef = "primarySqlSessionTemplate") public class DataSourcePrimaryConfig { - @Bean(name = "primaryDataSource") - @ConfigurationProperties(prefix = "spring.datasource.primary") + @Autowired + private DruidConfigPrimaryProperties druidConfigPrimaryProperties; + + @Bean(name = "primaryDataSource",initMethod = "init", destroyMethod = "close") @Primary - public DataSource testDataSource() { - return DataSourceBuilder.create().build(); + public DataSource testDataSource() throws SQLException { + DruidDataSource druidDataSource = new DruidDataSource(); + druidDataSource.setDriverClassName(druidConfigPrimaryProperties.getDriverClassName()); + druidDataSource.setUrl(druidConfigPrimaryProperties.getUrl()); + druidDataSource.setUsername(druidConfigPrimaryProperties.getUsername()); + druidDataSource.setPassword(druidConfigPrimaryProperties.getPassword()); + druidDataSource.setInitialSize(druidConfigPrimaryProperties.getMinIdle()); + druidDataSource.setMinIdle(druidConfigPrimaryProperties.getMinIdle()); + druidDataSource.setMaxActive(druidConfigPrimaryProperties.getMaxActive()); + druidDataSource.setMaxWait(druidConfigPrimaryProperties.getMaxWait()); + druidDataSource.setTimeBetweenEvictionRunsMillis(druidConfigPrimaryProperties.getTimeBetweenEvictionRunsMillis()); + druidDataSource.setMinEvictableIdleTimeMillis(druidConfigPrimaryProperties.getMinEvictableIdleTimeMillis()); + druidDataSource.setValidationQuery(druidConfigPrimaryProperties.getValidationQuery()); + druidDataSource.setTestWhileIdle(druidConfigPrimaryProperties.getTestWhileIdle()); + druidDataSource.setTestOnBorrow(druidConfigPrimaryProperties.getTestOnBorrow()); + druidDataSource.setTestOnReturn(druidConfigPrimaryProperties.getTestOnReturn()); + druidDataSource.setPoolPreparedStatements(druidConfigPrimaryProperties.getPoolPreparedStatements()); + druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(druidConfigPrimaryProperties.getMaxPoolPreparedStatementPerConnectionSize()); + druidDataSource.setFilters(druidConfigPrimaryProperties.getFilters()); + return druidDataSource; } @Bean(name = "primarySqlSessionFactory") diff --git a/quick-multi-data/src/main/java/com/quick/mulit/config/DataSourceSecondaryConfig.java b/quick-multi-data/src/main/java/com/quick/mulit/config/DataSourceSecondaryConfig.java index fb8997fd..e1cd8787 100644 --- a/quick-multi-data/src/main/java/com/quick/mulit/config/DataSourceSecondaryConfig.java +++ b/quick-multi-data/src/main/java/com/quick/mulit/config/DataSourceSecondaryConfig.java @@ -1,18 +1,19 @@ package com.quick.mulit.config; +import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; +import java.sql.SQLException; /** * Created with IDEA @@ -25,10 +26,30 @@ @MapperScan(basePackages = "com.quick.mulit.mapper.secondary", sqlSessionTemplateRef = "secondarySqlSessionTemplate") public class DataSourceSecondaryConfig { - @Bean(name = "secondaryDataSource") - @ConfigurationProperties(prefix = "spring.datasource.secondary") - public DataSource testDataSource() { - return DataSourceBuilder.create().build(); + @Autowired + private DruidConfigSecondaryProperties druidConfigSecondaryProperties; + + @Bean(name = "secondaryDataSource",initMethod = "init", destroyMethod = "close") + public DataSource testDataSource() throws SQLException { + DruidDataSource druidDataSource = new DruidDataSource(); + druidDataSource.setDriverClassName(druidConfigSecondaryProperties.getDriverClassName()); + druidDataSource.setUrl(druidConfigSecondaryProperties.getUrl()); + druidDataSource.setUsername(druidConfigSecondaryProperties.getUsername()); + druidDataSource.setPassword(druidConfigSecondaryProperties.getPassword()); + druidDataSource.setInitialSize(druidConfigSecondaryProperties.getMinIdle()); + druidDataSource.setMinIdle(druidConfigSecondaryProperties.getMinIdle()); + druidDataSource.setMaxActive(druidConfigSecondaryProperties.getMaxActive()); + druidDataSource.setMaxWait(druidConfigSecondaryProperties.getMaxWait()); + druidDataSource.setTimeBetweenEvictionRunsMillis(druidConfigSecondaryProperties.getTimeBetweenEvictionRunsMillis()); + druidDataSource.setMinEvictableIdleTimeMillis(druidConfigSecondaryProperties.getMinEvictableIdleTimeMillis()); + druidDataSource.setValidationQuery(druidConfigSecondaryProperties.getValidationQuery()); + druidDataSource.setTestWhileIdle(druidConfigSecondaryProperties.getTestWhileIdle()); + druidDataSource.setTestOnBorrow(druidConfigSecondaryProperties.getTestOnBorrow()); + druidDataSource.setTestOnReturn(druidConfigSecondaryProperties.getTestOnReturn()); + druidDataSource.setPoolPreparedStatements(druidConfigSecondaryProperties.getPoolPreparedStatements()); + druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(druidConfigSecondaryProperties.getMaxPoolPreparedStatementPerConnectionSize()); + druidDataSource.setFilters(druidConfigSecondaryProperties.getFilters()); + return druidDataSource; } @Bean(name = "secondarySqlSessionFactory") diff --git a/quick-multi-data/src/main/java/com/quick/mulit/config/DruidConfigPrimaryProperties.java b/quick-multi-data/src/main/java/com/quick/mulit/config/DruidConfigPrimaryProperties.java new file mode 100644 index 00000000..15836c8c --- /dev/null +++ b/quick-multi-data/src/main/java/com/quick/mulit/config/DruidConfigPrimaryProperties.java @@ -0,0 +1,161 @@ +package com.quick.mulit.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Created with IDEA + * User: vector + * Data: 2017/11/15 + * Time: 11:52 + * Description: + */ +@Component +@ConfigurationProperties(prefix = "spring.datasource.primary") +public class DruidConfigPrimaryProperties { + private String driverClassName; + private String url; + private String username; + private String password; + + private Integer minIdle; + private Integer maxActive; + private Integer maxWait; + private Long timeBetweenEvictionRunsMillis; + private Long minEvictableIdleTimeMillis; + private String validationQuery; + private Boolean testWhileIdle; + private Boolean testOnBorrow; + private Boolean testOnReturn; + private Boolean poolPreparedStatements; + private Integer maxPoolPreparedStatementPerConnectionSize; + private String filters; + + public String getDriverClassName() { + return driverClassName; + } + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Integer getMinIdle() { + return minIdle; + } + + public void setMinIdle(Integer minIdle) { + this.minIdle = minIdle; + } + + public Integer getMaxActive() { + return maxActive; + } + + public void setMaxActive(Integer maxActive) { + this.maxActive = maxActive; + } + + public Integer getMaxWait() { + return maxWait; + } + + public void setMaxWait(Integer maxWait) { + this.maxWait = maxWait; + } + + public Long getTimeBetweenEvictionRunsMillis() { + return timeBetweenEvictionRunsMillis; + } + + public void setTimeBetweenEvictionRunsMillis(Long timeBetweenEvictionRunsMillis) { + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + } + + public Long getMinEvictableIdleTimeMillis() { + return minEvictableIdleTimeMillis; + } + + public void setMinEvictableIdleTimeMillis(Long minEvictableIdleTimeMillis) { + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + public String getValidationQuery() { + return validationQuery; + } + + public void setValidationQuery(String validationQuery) { + this.validationQuery = validationQuery; + } + + public Boolean getTestWhileIdle() { + return testWhileIdle; + } + + public void setTestWhileIdle(Boolean testWhileIdle) { + this.testWhileIdle = testWhileIdle; + } + + public Boolean getTestOnBorrow() { + return testOnBorrow; + } + + public void setTestOnBorrow(Boolean testOnBorrow) { + this.testOnBorrow = testOnBorrow; + } + + public Boolean getTestOnReturn() { + return testOnReturn; + } + + public void setTestOnReturn(Boolean testOnReturn) { + this.testOnReturn = testOnReturn; + } + + public Boolean getPoolPreparedStatements() { + return poolPreparedStatements; + } + + public void setPoolPreparedStatements(Boolean poolPreparedStatements) { + this.poolPreparedStatements = poolPreparedStatements; + } + + public Integer getMaxPoolPreparedStatementPerConnectionSize() { + return maxPoolPreparedStatementPerConnectionSize; + } + + public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) { + this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize; + } + + public String getFilters() { + return filters; + } + + public void setFilters(String filters) { + this.filters = filters; + } +} diff --git a/quick-multi-data/src/main/java/com/quick/mulit/config/DruidConfigSecondaryProperties.java b/quick-multi-data/src/main/java/com/quick/mulit/config/DruidConfigSecondaryProperties.java new file mode 100644 index 00000000..75faf871 --- /dev/null +++ b/quick-multi-data/src/main/java/com/quick/mulit/config/DruidConfigSecondaryProperties.java @@ -0,0 +1,161 @@ +package com.quick.mulit.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Created with IDEA + * User: vector + * Data: 2017/11/15 + * Time: 11:52 + * Description: + */ +@Component +@ConfigurationProperties(prefix = "spring.datasource.secondary") +public class DruidConfigSecondaryProperties { + private String driverClassName; + private String url; + private String username; + private String password; + + private Integer minIdle; + private Integer maxActive; + private Integer maxWait; + private Long timeBetweenEvictionRunsMillis; + private Long minEvictableIdleTimeMillis; + private String validationQuery; + private Boolean testWhileIdle; + private Boolean testOnBorrow; + private Boolean testOnReturn; + private Boolean poolPreparedStatements; + private Integer maxPoolPreparedStatementPerConnectionSize; + private String filters; + + public String getDriverClassName() { + return driverClassName; + } + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Integer getMinIdle() { + return minIdle; + } + + public void setMinIdle(Integer minIdle) { + this.minIdle = minIdle; + } + + public Integer getMaxActive() { + return maxActive; + } + + public void setMaxActive(Integer maxActive) { + this.maxActive = maxActive; + } + + public Integer getMaxWait() { + return maxWait; + } + + public void setMaxWait(Integer maxWait) { + this.maxWait = maxWait; + } + + public Long getTimeBetweenEvictionRunsMillis() { + return timeBetweenEvictionRunsMillis; + } + + public void setTimeBetweenEvictionRunsMillis(Long timeBetweenEvictionRunsMillis) { + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + } + + public Long getMinEvictableIdleTimeMillis() { + return minEvictableIdleTimeMillis; + } + + public void setMinEvictableIdleTimeMillis(Long minEvictableIdleTimeMillis) { + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + public String getValidationQuery() { + return validationQuery; + } + + public void setValidationQuery(String validationQuery) { + this.validationQuery = validationQuery; + } + + public Boolean getTestWhileIdle() { + return testWhileIdle; + } + + public void setTestWhileIdle(Boolean testWhileIdle) { + this.testWhileIdle = testWhileIdle; + } + + public Boolean getTestOnBorrow() { + return testOnBorrow; + } + + public void setTestOnBorrow(Boolean testOnBorrow) { + this.testOnBorrow = testOnBorrow; + } + + public Boolean getTestOnReturn() { + return testOnReturn; + } + + public void setTestOnReturn(Boolean testOnReturn) { + this.testOnReturn = testOnReturn; + } + + public Boolean getPoolPreparedStatements() { + return poolPreparedStatements; + } + + public void setPoolPreparedStatements(Boolean poolPreparedStatements) { + this.poolPreparedStatements = poolPreparedStatements; + } + + public Integer getMaxPoolPreparedStatementPerConnectionSize() { + return maxPoolPreparedStatementPerConnectionSize; + } + + public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) { + this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize; + } + + public String getFilters() { + return filters; + } + + public void setFilters(String filters) { + this.filters = filters; + } +} diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/config/DruidStatFilter.java b/quick-multi-data/src/main/java/com/quick/mulit/config/DruidStatFilter.java similarity index 92% rename from quick-mybatis-druid/src/main/java/com/quick/druid/config/DruidStatFilter.java rename to quick-multi-data/src/main/java/com/quick/mulit/config/DruidStatFilter.java index b0191cca..9576c05b 100644 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/config/DruidStatFilter.java +++ b/quick-multi-data/src/main/java/com/quick/mulit/config/DruidStatFilter.java @@ -1,4 +1,4 @@ -package com.quick.druid.config; +package com.quick.mulit.config; import com.alibaba.druid.support.http.WebStatFilter; diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/config/DruidStatViewServlet.java b/quick-multi-data/src/main/java/com/quick/mulit/config/DruidStatViewServlet.java similarity index 96% rename from quick-mybatis-druid/src/main/java/com/quick/druid/config/DruidStatViewServlet.java rename to quick-multi-data/src/main/java/com/quick/mulit/config/DruidStatViewServlet.java index 179b6ea7..1d6b0380 100644 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/config/DruidStatViewServlet.java +++ b/quick-multi-data/src/main/java/com/quick/mulit/config/DruidStatViewServlet.java @@ -1,4 +1,4 @@ -package com.quick.druid.config; +package com.quick.mulit.config; import com.alibaba.druid.support.http.StatViewServlet; diff --git a/quick-multi-data/src/main/java/com/quick/mulit/mapper/secondary/ReaderMapper.java b/quick-multi-data/src/main/java/com/quick/mulit/mapper/secondary/ReaderMapper.java new file mode 100644 index 00000000..67c2046a --- /dev/null +++ b/quick-multi-data/src/main/java/com/quick/mulit/mapper/secondary/ReaderMapper.java @@ -0,0 +1,33 @@ +package com.quick.mulit.mapper.secondary; + +import com.quick.mulit.entity.secondary.Reader; +import com.quick.mulit.entity.secondary.ReaderExample; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface ReaderMapper { + int countByExample(ReaderExample example); + + int deleteByExample(ReaderExample example); + + int deleteByPrimaryKey(Integer id); + + int insert(Reader record); + + int insertSelective(Reader record); + + List selectByExample(ReaderExample example); + + Reader selectByPrimaryKey(Integer id); + + int updateByExampleSelective(@Param("record") Reader record, @Param("example") ReaderExample example); + + int updateByExample(@Param("record") Reader record, @Param("example") ReaderExample example); + + int updateByPrimaryKeySelective(Reader record); + + int updateByPrimaryKey(Reader record); +} \ No newline at end of file diff --git a/quick-multi-data/src/main/resources/application.properties b/quick-multi-data/src/main/resources/application.properties index e78cfb86..47047a6a 100644 --- a/quick-multi-data/src/main/resources/application.properties +++ b/quick-multi-data/src/main/resources/application.properties @@ -1,7 +1,7 @@ server.port=8080 # 主数据源,默认的 -spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.primary.url=jdbc:mysql://localhost:3306/sakila spring.datasource.primary.username=root spring.datasource.primary.password=root123 @@ -13,36 +13,34 @@ spring.datasource.secondary.username=root spring.datasource.secondary.password=root123 # 初始化大小,最小,最大 -spring.datasource.primary.initialSize=5 -spring.datasource.primary.maxIdle=20 -spring.datasource.primary.maxActive=20 + +#最小连接数量 +spring.datasource.primary.minIdle=2 +spring.datasource.primary.maxActive=5 spring.datasource.primary.maxWait=60000 -spring.datasource.primary.timeBetweenEvictionRunsMillis=3600000 -spring.datasource.primary.minEvictableIdleTimeMillis=18000000 -spring.datasource.primary.validationQuery=SELECT 1 FROM DUAL +spring.datasource.primary.timeBetweenEvictionRunsMillis=60000 +spring.datasource.primary.minEvictableIdleTimeMillis=300000 +spring.datasource.primary.validationQuery=SELECT 'x' FROM DUAL spring.datasource.primary.testWhileIdle=true spring.datasource.primary.testOnBorrow=false spring.datasource.primary.testOnReturn=false spring.datasource.primary.poolPreparedStatements=true spring.datasource.primary.maxPoolPreparedStatementPerConnectionSize=20 -spring.datasource.primary.filters=stat,wall,log4j -spring.datasource.primary.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 +spring.datasource.primary.filters=stat # 初始化大小,最小,最大 -spring.datasource.secondary.initialSize=5 -spring.datasource.secondary.maxIdle=20 -spring.datasource.secondary.maxActive=20 +spring.datasource.secondary.minIdle=2 +spring.datasource.secondary.maxActive=5 spring.datasource.secondary.maxWait=60000 -spring.datasource.secondary.timeBetweenEvictionRunsMillis=3600000 -spring.datasource.secondary.minEvictableIdleTimeMillis=18000000 -spring.datasource.secondary.validationQuery=SELECT 1 FROM DUAL +spring.datasource.secondary.timeBetweenEvictionRunsMillis=60000 +spring.datasource.secondary.minEvictableIdleTimeMillis=300000 +spring.datasource.secondary.validationQuery=SELECT 'x' FROM DUAL spring.datasource.secondary.testWhileIdle=true spring.datasource.secondary.testOnBorrow=false spring.datasource.secondary.testOnReturn=false spring.datasource.secondary.poolPreparedStatements=true spring.datasource.secondary.maxPoolPreparedStatementPerConnectionSize=20 -spring.datasource.secondary.filters=stat,wall,log4j -spring.datasource.secondary.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 +spring.datasource.secondary.filters=stat ## Mybatis 配置 #mybatis.typeAliasesPackage=com.quick.mulit.entity diff --git a/quick-ElasticSearch/src/main/java/com/quick/es/mapper/CityMapper.xml b/quick-multi-data/src/main/resources/mapper/secondary/ReaderMapper.xml similarity index 57% rename from quick-ElasticSearch/src/main/java/com/quick/es/mapper/CityMapper.xml rename to quick-multi-data/src/main/resources/mapper/secondary/ReaderMapper.xml index 40190c6e..5afb5c10 100644 --- a/quick-ElasticSearch/src/main/java/com/quick/es/mapper/CityMapper.xml +++ b/quick-multi-data/src/main/resources/mapper/secondary/ReaderMapper.xml @@ -1,12 +1,11 @@ - - - - - - - + + + + + + @@ -67,15 +66,15 @@ - ID, Name, CountryCode, District, Population + id, firstName, lastName, random_num - select distinct - from city + from reader @@ -86,85 +85,76 @@ - delete from city - where ID = #{id,jdbcType=INTEGER} + delete from reader + where id = #{id,jdbcType=INTEGER} - - delete from city + + delete from reader - - insert into city (ID, Name, CountryCode, - District, Population) - values (#{id,jdbcType=INTEGER}, #{name,jdbcType=CHAR}, #{countrycode,jdbcType=CHAR}, - #{district,jdbcType=CHAR}, #{population,jdbcType=INTEGER}) + + insert into reader (id, firstName, lastName, + random_num) + values (#{id,jdbcType=INTEGER}, #{firstname,jdbcType=VARCHAR}, #{lastname,jdbcType=VARCHAR}, + #{randomNum,jdbcType=VARCHAR}) - - insert into city + + insert into reader - ID, + id, - - Name, + + firstName, - - CountryCode, + + lastName, - - District, - - - Population, + + random_num, #{id,jdbcType=INTEGER}, - - #{name,jdbcType=CHAR}, - - - #{countrycode,jdbcType=CHAR}, + + #{firstname,jdbcType=VARCHAR}, - - #{district,jdbcType=CHAR}, + + #{lastname,jdbcType=VARCHAR}, - - #{population,jdbcType=INTEGER}, + + #{randomNum,jdbcType=VARCHAR}, - + select count(*) from reader - update city + update reader - ID = #{record.id,jdbcType=INTEGER}, + id = #{record.id,jdbcType=INTEGER}, - - Name = #{record.name,jdbcType=CHAR}, + + firstName = #{record.firstname,jdbcType=VARCHAR}, - - CountryCode = #{record.countrycode,jdbcType=CHAR}, + + lastName = #{record.lastname,jdbcType=VARCHAR}, - - District = #{record.district,jdbcType=CHAR}, - - - Population = #{record.population,jdbcType=INTEGER}, + + random_num = #{record.randomNum,jdbcType=VARCHAR}, @@ -172,40 +162,35 @@ - update city - set ID = #{record.id,jdbcType=INTEGER}, - Name = #{record.name,jdbcType=CHAR}, - CountryCode = #{record.countrycode,jdbcType=CHAR}, - District = #{record.district,jdbcType=CHAR}, - Population = #{record.population,jdbcType=INTEGER} + update reader + set id = #{record.id,jdbcType=INTEGER}, + firstName = #{record.firstname,jdbcType=VARCHAR}, + lastName = #{record.lastname,jdbcType=VARCHAR}, + random_num = #{record.randomNum,jdbcType=VARCHAR} - - update city + + update reader - - Name = #{name,jdbcType=CHAR}, - - - CountryCode = #{countrycode,jdbcType=CHAR}, + + firstName = #{firstname,jdbcType=VARCHAR}, - - District = #{district,jdbcType=CHAR}, + + lastName = #{lastname,jdbcType=VARCHAR}, - - Population = #{population,jdbcType=INTEGER}, + + random_num = #{randomNum,jdbcType=VARCHAR}, - where ID = #{id,jdbcType=INTEGER} + where id = #{id,jdbcType=INTEGER} - - update city - set Name = #{name,jdbcType=CHAR}, - CountryCode = #{countrycode,jdbcType=CHAR}, - District = #{district,jdbcType=CHAR}, - Population = #{population,jdbcType=INTEGER} - where ID = #{id,jdbcType=INTEGER} + + update reader + set firstName = #{firstname,jdbcType=VARCHAR}, + lastName = #{lastname,jdbcType=VARCHAR}, + random_num = #{randomNum,jdbcType=VARCHAR} + where id = #{id,jdbcType=INTEGER} \ No newline at end of file diff --git a/quick-multi-rabbitmq/pom.xml b/quick-multi-rabbitmq/pom.xml new file mode 100644 index 00000000..7df7e1f5 --- /dev/null +++ b/quick-multi-rabbitmq/pom.xml @@ -0,0 +1,47 @@ + + + quick-multi-rabbitmq + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 1.5.7.RELEASE + + + + 2.2.2 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-amqp + + + + org.projectlombok + lombok + 1.16.20 + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + \ No newline at end of file diff --git a/quick-spark/src/main/java/spark/Application.java b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/Application.java similarity index 78% rename from quick-spark/src/main/java/spark/Application.java rename to quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/Application.java index 025ce168..f5f2572a 100644 --- a/quick-spark/src/main/java/spark/Application.java +++ b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/Application.java @@ -1,14 +1,14 @@ -package spark; +package com.multi.rabbitmq; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** - * Created by wangxc on 2017/3/9. + * @author vector + * @date: 2019/4/11 0011 10:34 */ @SpringBootApplication public class Application { - public static void main(String[] args) { SpringApplication.run(Application.class, args); } diff --git a/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/config/RabbitMqConfiguration.java b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/config/RabbitMqConfiguration.java new file mode 100644 index 00000000..46b088cb --- /dev/null +++ b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/config/RabbitMqConfiguration.java @@ -0,0 +1,138 @@ +package com.multi.rabbitmq.config; + +import com.multi.rabbitmq.mq.RabbitMqConsumer; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * @author vector + * @date: 2019/4/11 0011 10:38 + */ +@Configuration +public class RabbitMqConfiguration { + + private int[] queuesConcurrency = {2,2,2,2,2,2}; + + @Autowired + private ApplicationContext applicationContext; + + @Bean(name = "firstConnectionFactory") + @Primary + public ConnectionFactory firstSyncConnectionFactory( + @Value("${spring.rabbitmq.first.host}") String host, + @Value("${spring.rabbitmq.first.port}") int port, + @Value("${spring.rabbitmq.first.username}") String username, + @Value("${spring.rabbitmq.first.password}") String password, + @Value("${spring.rabbitmq.first.virtual-host}") String virtualHost) { + + + return getCachingConnectionFactory(host, port, username, password, virtualHost); + } + +// @Bean(name = "firstContainerFactory") +// public SimpleRabbitListenerContainerFactory firstFactory( +// SimpleRabbitListenerContainerFactoryConfigurer configurer, +// @Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory) { +// SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); +// factory.setConcurrentConsumers(1); +// factory.setReceiveTimeout(1000L); +// configurer.configure(factory, connectionFactory); +// return factory; +// } + + /** + * 动态创建队列对应的消费端,可以自动分配并发数 + * @return + */ + @Bean + public Runnable dynamic4TalentMQConfiguration(){ + ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext; + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); + for (int i = 0; i < queuesConcurrency.length; i++) { + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(SimpleRabbitListenerContainerFactory.class); + /** + * 设置属性 + */ + beanDefinitionBuilder.addPropertyValue("connectionFactory", applicationContext.getBean("firstConnectionFactory")); + beanDefinitionBuilder.addPropertyValue("concurrentConsumers", queuesConcurrency[i]); + beanDefinitionBuilder.addPropertyValue("receiveTimeout", 1000L); + + /** + * 注册到spring容器中 + */ + beanFactory.registerBeanDefinition("jmsQueue4TalentIdContainer" + i, beanDefinitionBuilder.getBeanDefinition()); + } + return null; + } + + @Bean + public RabbitMqConsumer initRabbitMqConsumer() { + return new RabbitMqConsumer(); + } + + + @Bean(name = "firstRabbitTemplate") + public RabbitTemplate firstRabbitTemplate( + @Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory) { + RabbitTemplate firstRabbitTemplate = new RabbitTemplate(connectionFactory); + //使用外部事物 + //ydtRabbitTemplate.setChannelTransacted(true); + return firstRabbitTemplate; + } + + @Bean(name = "secondConnectionFactory") + public ConnectionFactory secondConnectionFactory( + @Value("${spring.rabbitmq.second.host}") String host, + @Value("${spring.rabbitmq.second.port}") int port, + @Value("${spring.rabbitmq.second.username}") String username, + @Value("${spring.rabbitmq.second.password}") String password, + @Value("${spring.rabbitmq.second.virtual-host}") String virtualHost) { + return getCachingConnectionFactory(host, port, username, password, virtualHost); + } + + @Bean(name = "secondRabbitTemplate") + @Primary + public RabbitTemplate secondRabbitTemplate( + @Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory) { + RabbitTemplate secondRabbitTemplate = new RabbitTemplate(connectionFactory); + //使用外部事物 + //lpzRabbitTemplate.setChannelTransacted(true); + return secondRabbitTemplate; + } + + @Bean(name = "secondContainerFactory") + public SimpleRabbitListenerContainerFactory secondFactory( + SimpleRabbitListenerContainerFactoryConfigurer configurer, + @Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory) { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + configurer.configure(factory, connectionFactory); + return factory; + } + + private CachingConnectionFactory getCachingConnectionFactory(String host, + int port, + String username, + String password, + String virtualHost) { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + connectionFactory.setHost(host); + connectionFactory.setPort(port); + connectionFactory.setUsername(username); + connectionFactory.setPassword(password); + connectionFactory.setVirtualHost(virtualHost); + return connectionFactory; + } +} diff --git a/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/config/RabbitMqInstance.java b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/config/RabbitMqInstance.java new file mode 100644 index 00000000..d4404f5b --- /dev/null +++ b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/config/RabbitMqInstance.java @@ -0,0 +1,8 @@ +package com.multi.rabbitmq.config; + +/** + * @author vector + * @date: 2019/4/11 0011 11:03 + */ +public class RabbitMqInstance { +} diff --git a/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/mq/RabbitMqConsumer.java b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/mq/RabbitMqConsumer.java new file mode 100644 index 00000000..968a8ce5 --- /dev/null +++ b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/mq/RabbitMqConsumer.java @@ -0,0 +1,47 @@ +package com.multi.rabbitmq.mq; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.annotation.RabbitListener; + +/** + * @author vector + * @date: 2019/4/11 0011 10:59 + */ +@Slf4j +public class RabbitMqConsumer { + + @RabbitListener(queues = {"${rabbit.mq.queue0}"}, containerFactory = "jmsQueue4TalentIdContainer0") + public void firstMq0(String msg) { + log.info("dev0 receive msg: {}", msg); + } + + @RabbitListener(queues = {"${rabbit.mq.queue1}"}, containerFactory = "jmsQueue4TalentIdContainer1") + public void firstMq1(String msg) { + log.info("dev1 receive msg: {}", msg); + } + + @RabbitListener(queues = {"${rabbit.mq.queue2}"}, containerFactory = "jmsQueue4TalentIdContainer2") + public void firstMq2(String msg) { + log.info("dev2 receive msg: {}", msg); + } + + @RabbitListener(queues = {"${rabbit.mq.queue3}"}, containerFactory = "jmsQueue4TalentIdContainer3") + public void firstMq3(String msg) { + log.info("dev3 receive msg: {}", msg); + } + + @RabbitListener(queues = {"${rabbit.mq.queue4}"}, containerFactory = "jmsQueue4TalentIdContainer4") + public void firstMq4(String msg) { + log.info("dev4 receive msg: {}", msg); + } + + @RabbitListener(queues = {"${rabbit.mq.queue5}"}, containerFactory = "jmsQueue4TalentIdContainer5") + public void firstMq5(String msg) { + log.info("dev5 receive msg: {}", msg); + } + +// @RabbitListener(queues = {"tbd_resume_id_rc_5"}, containerFactory = "secondContainerFactory") +// public void secondMq(String msg) { +// log.info("rc receive msg: {}", msg); +// } +} diff --git a/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/mq/RabbitMqProducer.java b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/mq/RabbitMqProducer.java new file mode 100644 index 00000000..668d6b0c --- /dev/null +++ b/quick-multi-rabbitmq/src/main/java/com/multi/rabbitmq/mq/RabbitMqProducer.java @@ -0,0 +1,36 @@ +package com.multi.rabbitmq.mq; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; + +/** + * @author vector + * @date: 2019/4/11 0011 11:20 + */ +@Component +@Slf4j +public class RabbitMqProducer { + + @Resource(name = "firstRabbitTemplate") + private RabbitTemplate firstRabbitTemplate; + + @Resource(name = "secondRabbitTemplate") + private RabbitTemplate secondRabbitTemplate; + + public void firstSend() { + String msg = "hello1 " + new Date(); + System.out.println("Sender : " + msg); + this.firstRabbitTemplate.convertAndSend("tbd_resume_id_dev_5", msg); + } + + public void secondSend() { + String msg = "hello2 " + new Date(); + System.out.println("Sender : " + msg); + this.secondRabbitTemplate.convertAndSend("tbd_resume_id_rc_5", msg); + } + +} diff --git a/quick-multi-rabbitmq/src/main/resources/application.yml b/quick-multi-rabbitmq/src/main/resources/application.yml new file mode 100644 index 00000000..6cc2d312 --- /dev/null +++ b/quick-multi-rabbitmq/src/main/resources/application.yml @@ -0,0 +1,23 @@ +spring: + rabbitmq: + first: + host: 192.168.68.11 + port: 5672 + username: tbddev + password: tbddev!#%2017 + virtual-host: /tbddev + second: + host: 192.168.68.11 + port: 5672 + username: tbdrc + password: tbdrc!#%2017 + virtual-host: /tbdrc + +rabbit: + mq: + queue0: tbd_resume_id_dev_0 + queue1: tbd_resume_id_dev_1 + queue2: tbd_resume_id_dev_2 + queue3: tbd_resume_id_dev_3 + queue4: tbd_resume_id_dev_4 + queue5: tbd_resume_id_dev_5 \ No newline at end of file diff --git a/quick-multi-rabbitmq/src/test/java/mq/RabbitMqProducerTest.java b/quick-multi-rabbitmq/src/test/java/mq/RabbitMqProducerTest.java new file mode 100644 index 00000000..638a211b --- /dev/null +++ b/quick-multi-rabbitmq/src/test/java/mq/RabbitMqProducerTest.java @@ -0,0 +1,35 @@ +package mq; + +import com.multi.rabbitmq.Application; +import com.multi.rabbitmq.mq.RabbitMqProducer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.annotation.Resource; + +/** + * @author vector + * @date: 2019/4/11 0011 11:28 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = Application.class) +public class RabbitMqProducerTest { + + @Resource + private RabbitMqProducer rabbitMqProducer; + + @Test + public void firstSend() { + for (int i = 0; i < 100; i++) { + rabbitMqProducer.firstSend(); + } + } + + @Test + public void secondSend() { + rabbitMqProducer.secondSend(); + } + +} diff --git a/quick-mybatis-druid/db/user.sql b/quick-mybatis-druid/db/user.sql new file mode 100644 index 00000000..e306547d --- /dev/null +++ b/quick-mybatis-druid/db/user.sql @@ -0,0 +1,8 @@ +CREATE TABLE `user` ( + `id` bigint(20) NOT NULL COMMENT '主键ID', + `name` varchar(30) DEFAULT NULL COMMENT '姓名', + `age` int(11) DEFAULT NULL COMMENT '年龄', + `email` varchar(50) DEFAULT NULL COMMENT '邮箱', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + diff --git a/quick-mybatis-druid/pom.xml b/quick-mybatis-druid/pom.xml index 73356da5..c4e04af4 100644 --- a/quick-mybatis-druid/pom.xml +++ b/quick-mybatis-druid/pom.xml @@ -3,64 +3,81 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> quick-mybatis-druid - com.quick 1.0-SNAPSHOT 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml UTF-8 UTF-8 1.8 - 5.1.30 org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - + spring-boot-starter org.springframework.boot - spring-boot-starter-log4j2 + spring-boot-starter-test + test - org.mybatis.spring.boot mybatis-spring-boot-starter - 1.1.1 - + + org.projectlombok + lombok + true + + + com.baomidou + mybatis-plus-boot-starter + + mysql mysql-connector-java - ${mysql.version} - + + + p6spy + p6spy + 3.8.6 + com.alibaba - druid - 1.0.18 + druid-spring-boot-starter + + - org.springframework.boot - spring-boot-starter-actuator + com.baomidou + mybatis-plus-generator + + org.freemarker + freemarker + + + + com.github.jsonzou + jmockdata + 2.4 + + + diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/Application.java b/quick-mybatis-druid/src/main/java/com/quick/druid/Application.java index 4ded28ac..e042c892 100644 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/Application.java +++ b/quick-mybatis-druid/src/main/java/com/quick/druid/Application.java @@ -1,9 +1,18 @@ package com.quick.druid; +import com.github.jsonzou.jmockdata.JMockData; +import com.quick.druid.sys.entity.User; +import com.quick.druid.sys.mapper.UserMapper; +import com.quick.druid.sys.service.IUserService; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; -import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.util.ArrayList; +import java.util.List; /** * @Author: wangxc @@ -13,11 +22,30 @@ * @wxid: BMHJQS */ @SpringBootApplication -@ServletComponentScan -@EnableTransactionManagement // 开始事务支持 -public class Application { +@MapperScan("com.quick.druid.*.mapper") +public class Application implements CommandLineRunner { + + @Autowired + private IUserService userService; public static void main(String[] args) { SpringApplication.run(Application.class,args); } + + + @Override + public void run(String... args) throws Exception { + User user = userService.getById(1); + System.out.println(user.toString()); + + List userList = new ArrayList<>(); + + for (int i = 0; i < 100; i++) { + User anyData= JMockData.mockSimpleType(User.class); + anyData.setId(null); + userList.add(anyData); + } + userService.saveBatch(userList); + + } } diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/controller/ApiController.java b/quick-mybatis-druid/src/main/java/com/quick/druid/controller/ApiController.java deleted file mode 100644 index e0cfd172..00000000 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/controller/ApiController.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.quick.druid.controller; - -import com.quick.druid.entity.City; -import com.quick.druid.service.CityService; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -@RestController -@Controller -public class ApiController { - - @Resource - private CityService cityService; - - - @RequestMapping("/city/{id}") - public City getCityById(@PathVariable("id")int id){ - return cityService.getCityById(id); - } - - @RequestMapping("/transaction/save") - public String saveTransaction() { - cityService.saveTransaction(); - return "saveTransaction"; - } - -} diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/entity/City.java b/quick-mybatis-druid/src/main/java/com/quick/druid/entity/City.java deleted file mode 100644 index 711f2fe1..00000000 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/entity/City.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.quick.druid.entity; - -public class City { - private Integer id; - - private String name; - - private String countrycode; - - private String district; - - private Integer population; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name == null ? null : name.trim(); - } - - public String getCountrycode() { - return countrycode; - } - - public void setCountrycode(String countrycode) { - this.countrycode = countrycode == null ? null : countrycode.trim(); - } - - public String getDistrict() { - return district; - } - - public void setDistrict(String district) { - this.district = district == null ? null : district.trim(); - } - - public Integer getPopulation() { - return population; - } - - public void setPopulation(Integer population) { - this.population = population; - } -} \ No newline at end of file diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/mapper/CityMapper.java b/quick-mybatis-druid/src/main/java/com/quick/druid/mapper/CityMapper.java deleted file mode 100644 index 6eef153f..00000000 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/mapper/CityMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.quick.druid.mapper; - -import com.quick.druid.entity.City; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface CityMapper { - int deleteByPrimaryKey(Integer id); - - int insert(City record); - - int insertSelective(City record); - - City selectByPrimaryKey(Integer id); - - int updateByPrimaryKeySelective(City record); - - int updateByPrimaryKey(City record); -} \ No newline at end of file diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/service/CityService.java b/quick-mybatis-druid/src/main/java/com/quick/druid/service/CityService.java deleted file mode 100644 index 9715dc64..00000000 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/service/CityService.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.quick.druid.service; - -import com.quick.druid.entity.City; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -public interface CityService { - - City getCityById(int id); - - void saveTransaction(); -} diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/service/impl/CityServiceImpl.java b/quick-mybatis-druid/src/main/java/com/quick/druid/service/impl/CityServiceImpl.java deleted file mode 100644 index 06997084..00000000 --- a/quick-mybatis-druid/src/main/java/com/quick/druid/service/impl/CityServiceImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.quick.druid.service.impl; - -import com.quick.druid.entity.City; -import com.quick.druid.entity.Country; -import com.quick.druid.mapper.CityMapper; -import com.quick.druid.mapper.CountryMapper; -import com.quick.druid.service.CityService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.UUID; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -@Service -public class CityServiceImpl implements CityService { - - @Autowired - private CityMapper cityMapper; - - @Autowired - private CountryMapper countryMapper; - - @Override - public City getCityById(int id) { - return cityMapper.selectByPrimaryKey(id); - } - - @Transactional(rollbackFor = Exception.class) // 需要回滚的地方加入注解 - @Override - public void saveTransaction() { - City city = new City(); - city.setCountrycode("HRV"); - city.setDistrict("20"); - city.setName("this is test"); - cityMapper.insertSelective(city); - - Country country = new Country(); - country.setName("this is test"); - country.setCode(UUID.fromString("vector").toString()); - - countryMapper.insertSelective(country); - } -} diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/sys/entity/User.java b/quick-mybatis-druid/src/main/java/com/quick/druid/sys/entity/User.java new file mode 100644 index 00000000..fa4f700a --- /dev/null +++ b/quick-mybatis-druid/src/main/java/com/quick/druid/sys/entity/User.java @@ -0,0 +1,44 @@ +package com.quick.druid.sys.entity; + +import java.io.Serializable; + +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + *

+ * + *

+ * + * @author vector4wang + * @since 2019-12-19 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +public class User implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId + private Long id; + + /** + * 姓名 + */ + private String name; + + /** + * 年龄 + */ + private Integer age; + + /** + * 邮箱 + */ + private String email; + + +} diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/sys/mapper/UserMapper.java b/quick-mybatis-druid/src/main/java/com/quick/druid/sys/mapper/UserMapper.java new file mode 100644 index 00000000..49eb8e0f --- /dev/null +++ b/quick-mybatis-druid/src/main/java/com/quick/druid/sys/mapper/UserMapper.java @@ -0,0 +1,16 @@ +package com.quick.druid.sys.mapper; + +import com.quick.druid.sys.entity.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Select; + +/** + *

+ * Mapper 接口 + *

+ * + * @author vector4wang + * @since 2019-12-19 + */ +public interface UserMapper extends BaseMapper { +} diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/sys/service/IUserService.java b/quick-mybatis-druid/src/main/java/com/quick/druid/sys/service/IUserService.java new file mode 100644 index 00000000..45b4f8f8 --- /dev/null +++ b/quick-mybatis-druid/src/main/java/com/quick/druid/sys/service/IUserService.java @@ -0,0 +1,16 @@ +package com.quick.druid.sys.service; + +import com.quick.druid.sys.entity.User; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 服务类 + *

+ * + * @author vector4wang + * @since 2019-12-19 + */ +public interface IUserService extends IService { + +} diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/sys/service/impl/UserServiceImpl.java b/quick-mybatis-druid/src/main/java/com/quick/druid/sys/service/impl/UserServiceImpl.java new file mode 100644 index 00000000..2d6c203d --- /dev/null +++ b/quick-mybatis-druid/src/main/java/com/quick/druid/sys/service/impl/UserServiceImpl.java @@ -0,0 +1,20 @@ +package com.quick.druid.sys.service.impl; + +import com.quick.druid.sys.entity.User; +import com.quick.druid.sys.mapper.UserMapper; +import com.quick.druid.sys.service.IUserService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 服务实现类 + *

+ * + * @author vector4wang + * @since 2019-12-19 + */ +@Service +public class UserServiceImpl extends ServiceImpl implements IUserService { + +} diff --git a/quick-mybatis-druid/src/main/java/com/quick/druid/utils/CodeGenerator.java b/quick-mybatis-druid/src/main/java/com/quick/druid/utils/CodeGenerator.java new file mode 100644 index 00000000..96ae925d --- /dev/null +++ b/quick-mybatis-druid/src/main/java/com/quick/druid/utils/CodeGenerator.java @@ -0,0 +1,135 @@ +package com.quick.druid.utils; + +import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.generator.AutoGenerator; +import com.baomidou.mybatisplus.generator.InjectionConfig; +import com.baomidou.mybatisplus.generator.config.*; +import com.baomidou.mybatisplus.generator.config.po.TableInfo; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class CodeGenerator { + + /** + *

+ * 读取控制台内容 + *

+ */ + public static String scanner(String tip) { + Scanner scanner = new Scanner(System.in); + StringBuilder help = new StringBuilder(); + help.append("请输入" + tip + ":"); + System.out.println(help.toString()); + if (scanner.hasNext()) { + String ipt = scanner.next(); + if (StringUtils.isNotEmpty(ipt)) { + return ipt; + } + } + throw new MybatisPlusException("请输入正确的" + tip + "!"); + } + + public static void main(String[] args) { + // 代码生成器 + AutoGenerator mpg = new AutoGenerator(); + + // 全局配置 + GlobalConfig gc = new GlobalConfig(); + String projectPath = System.getProperty("user.dir"); + String currentModulesPath = scanner("项目模块名:"); + gc.setOutputDir(projectPath + "/" + currentModulesPath + "/src/main/java"); + gc.setAuthor("vector4wang"); + gc.setOpen(false); + // gc.setSwagger2(true); 实体属性 Swagger2 注解 + mpg.setGlobalConfig(gc); + + // 数据源配置 + DataSourceConfig dsc = new DataSourceConfig(); + dsc.setUrl("jdbc:mysql://192.168.12.128:3306/world?useUnicode=true&useSSL=false&characterEncoding=utf8"); + // dsc.setSchemaName("public"); + dsc.setDriverName("com.mysql.jdbc.Driver"); + dsc.setUsername("root"); + dsc.setPassword("123456"); + mpg.setDataSource(dsc); + + // 包配置 + PackageConfig pc = new PackageConfig(); + pc.setModuleName(scanner("包名")); + pc.setParent("com.quick.druid"); + mpg.setPackageInfo(pc); + + // 自定义配置 + InjectionConfig cfg = new InjectionConfig() { + @Override + public void initMap() { + // to do nothing + } + }; + + // 如果模板引擎是 freemarker + String templatePath = "/templates/mapper.xml.ftl"; + // 如果模板引擎是 velocity + // String templatePath = "/templates/mapper.xml.vm"; + + // 自定义输出配置 + List focList = new ArrayList<>(); + // 自定义配置会被优先输出 + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! + return projectPath + "/" + currentModulesPath + "/src/main/resources/mapper/" + pc.getModuleName() + + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; + } + }); + /* + cfg.setFileCreate(new IFileCreate() { + @Override + public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { + // 判断自定义文件夹是否需要创建 + checkDir("调用默认方法创建的目录"); + return false; + } + }); + */ + cfg.setFileOutConfigList(focList); + mpg.setCfg(cfg); + + // 配置模板 + TemplateConfig templateConfig = new TemplateConfig(); + + // 配置自定义输出模板 + //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 + // templateConfig.setEntity("templates/entity2.java"); + // templateConfig.setService(); + // templateConfig.setController(); + + templateConfig.setXml(null); + mpg.setTemplate(templateConfig); + + // 策略配置 + StrategyConfig strategy = new StrategyConfig(); + strategy.setNaming(NamingStrategy.underline_to_camel); + strategy.setColumnNaming(NamingStrategy.underline_to_camel); +// strategy.setSuperEntityClass("com.baomidou.ant.common.BaseEntity"); + strategy.setEntityLombokModel(true); + strategy.setRestControllerStyle(true); + // 公共父类 +// strategy.setSuperControllerClass("com.baomidou.ant.common.BaseController"); + // 写于父类中的公共字段 + strategy.setSuperEntityColumns("id"); + strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); + strategy.setControllerMappingHyphenStyle(true); + strategy.setTablePrefix(pc.getModuleName() + "_"); + mpg.setStrategy(strategy); + mpg.setTemplateEngine(new FreemarkerTemplateEngine()); + mpg.execute(); + } + +} \ No newline at end of file diff --git a/quick-mybatis-druid/src/main/resources/application.properties b/quick-mybatis-druid/src/main/resources/application.properties index f57c9c0f..1a26e29d 100644 --- a/quick-mybatis-druid/src/main/resources/application.properties +++ b/quick-mybatis-druid/src/main/resources/application.properties @@ -1,7 +1,9 @@ #Server -server.port=80 -#LOG -logging.config=classpath:log4j2.xml +server.port=8080 + +#show sql +logging.level.com.quick.druid.mapper=debug + #MYBATIS mybatis.type-aliases-package=com.lance.mybatis.domain @@ -12,15 +14,13 @@ mybatis.configuration.use-generated-keys=true mybatis.configuration.default-fetch-size=100 mybatis.configuration.default-statement-timeout=30 -#DATASOURCE -spring.datasource.schema=classpath:init-sql/schema.sql -spring.datasource.continueOnError=true - -spring.datasource.type=com.alibaba.druid.pool.DruidDataSource -spring.datasource.url=jdbc:mysql://localhost/world +#DATASOURCE use docker [docker run --name sb-quick-mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3307:3306 -d mysql:5.7] +spring.datasource.url=jdbc:mysql://localhost:3308/world spring.datasource.username=root -spring.datasource.password=root123 -spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.password=123456 +## 5.7 +#spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.initialSize=5 spring.datasource.minIdle=5 diff --git a/quick-mybatis-druid/src/main/resources/application.yml b/quick-mybatis-druid/src/main/resources/application.yml new file mode 100644 index 00000000..8f1a975c --- /dev/null +++ b/quick-mybatis-druid/src/main/resources/application.yml @@ -0,0 +1,11 @@ +# DataSource Config +spring: + datasource: + driver-class-name: com.mysql.jdbc.Driver + url: jdbc:mysql://192.168.12.128:3306/world + username: root + password: 123456 + +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl \ No newline at end of file diff --git a/quick-mybatis-druid/src/main/resources/log4j2.xml b/quick-mybatis-druid/src/main/resources/log4j2.xml deleted file mode 100644 index a28a30a5..00000000 --- a/quick-mybatis-druid/src/main/resources/log4j2.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/quick-mybatis-druid/src/main/resources/logback-spring.xml b/quick-mybatis-druid/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..b61443de --- /dev/null +++ b/quick-mybatis-druid/src/main/resources/logback-spring.xml @@ -0,0 +1,56 @@ + + + logback + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + log/app.log + + logs/app-%i-%d{yyyy-MM-dd}.log + 30 + + + 10MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + ERROR + + log/error.log + + logs/error-%i-%d{yyyy-MM-dd}.log + 30 + + + 10MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/quick-mybatis-druid/src/main/resources/mapper/CityMapper.xml b/quick-mybatis-druid/src/main/resources/mapper/CityMapper.xml deleted file mode 100644 index b8580bc0..00000000 --- a/quick-mybatis-druid/src/main/resources/mapper/CityMapper.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - ID, Name, CountryCode, District, Population - - - - delete from city - where ID = #{id,jdbcType=INTEGER} - - - insert into city (Name, CountryCode, District, - Population) - values (#{name,jdbcType=CHAR}, #{countrycode,jdbcType=CHAR}, #{district,jdbcType=CHAR}, - #{population,jdbcType=INTEGER}) - - - insert into city - - - Name, - - - CountryCode, - - - District, - - - Population, - - - - - #{name,jdbcType=CHAR}, - - - #{countrycode,jdbcType=CHAR}, - - - #{district,jdbcType=CHAR}, - - - #{population,jdbcType=INTEGER}, - - - - - update city - - - Name = #{name,jdbcType=CHAR}, - - - CountryCode = #{countrycode,jdbcType=CHAR}, - - - District = #{district,jdbcType=CHAR}, - - - Population = #{population,jdbcType=INTEGER}, - - - where ID = #{id,jdbcType=INTEGER} - - - update city - set Name = #{name,jdbcType=CHAR}, - CountryCode = #{countrycode,jdbcType=CHAR}, - District = #{district,jdbcType=CHAR}, - Population = #{population,jdbcType=INTEGER} - where ID = #{id,jdbcType=INTEGER} - - \ No newline at end of file diff --git a/quick-mybatis-druid/src/main/resources/mapper/sys/UserMapper.xml b/quick-mybatis-druid/src/main/resources/mapper/sys/UserMapper.xml new file mode 100644 index 00000000..b8af3b1c --- /dev/null +++ b/quick-mybatis-druid/src/main/resources/mapper/sys/UserMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/quick-oauth2/pom.xml b/quick-oauth2/pom.xml index 9e7f4359..d489de59 100644 --- a/quick-oauth2/pom.xml +++ b/quick-oauth2/pom.xml @@ -5,7 +5,7 @@ quick-oauth2 quick-oauth2-server - quick-oauth2-baiidu + quick-github-oauth com.quick 1.0-SNAPSHOT diff --git a/quick-oauth2/quick-github-oauth/pom.xml b/quick-oauth2/quick-github-oauth/pom.xml new file mode 100644 index 00000000..ab602b97 --- /dev/null +++ b/quick-oauth2/quick-github-oauth/pom.xml @@ -0,0 +1,70 @@ + + + quick-github-oauth + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 1.3.5.RELEASE + + + + + UTF-8 + 1.8 + 2.0.3.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security.oauth + spring-security-oauth2 + + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/Application.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/Application.java new file mode 100644 index 00000000..7f3e8978 --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/Application.java @@ -0,0 +1,38 @@ +package com.github.oauth; + +import com.github.oauth.user.User; +import com.github.oauth.user.UserRepo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Created with IDEA + * User: vector + * Data: 2017/10/30 + * Time: 16:40 + * Description: + */ +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Autowired + UserRepo userRepo; + + @Autowired + public void init(){ + try { + User user=new User(); + user.setUsername("vector"); + user.setPassword("123456"); + user.setRole("ROLE_USER"); + userRepo.save(user); + + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/common/ICommonService.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/common/ICommonService.java new file mode 100644 index 00000000..c3df2416 --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/common/ICommonService.java @@ -0,0 +1,18 @@ +package com.github.oauth.common; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +public interface ICommonService { + T save(T entity) throws Exception; + void delete(Long id) throws Exception; + void delete(T entity) throws Exception; + T findById(Long id); + T findBySample(T sample); + List findAll(); + List findAll(T sample); + Page findAll(PageRequest pageRequest); + Page findAll(T sample, PageRequest pageRequest); +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/config/ServletInitializer.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/config/ServletInitializer.java new file mode 100644 index 00000000..2af0ae8e --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/config/ServletInitializer.java @@ -0,0 +1,13 @@ +package com.github.oauth.config; + +import com.github.oauth.Application; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.web.SpringBootServletInitializer; + +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(Application.class); + } +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/config/WebSecurityConfig.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/config/WebSecurityConfig.java new file mode 100644 index 00000000..15c1bd4a --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/config/WebSecurityConfig.java @@ -0,0 +1,93 @@ +package com.github.oauth.config; + +import com.github.oauth.user.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; +import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices; +import org.springframework.boot.context.embedded.FilterRegistrationBean; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.oauth2.client.OAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter; +import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import javax.servlet.Filter; + +@Configuration +@EnableOAuth2Client +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private UserService userService; + + @Autowired + OAuth2ClientContext oauth2ClientContext; + + @Override + protected void configure(AuthenticationManagerBuilder auth) + throws Exception { + // Configure spring security's authenticationManager with custom + // user details service + auth.userDetailsService(this.userService); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/user/**").authenticated() + .anyRequest().permitAll() + .and().exceptionHandling() + .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) +// .and() +// .formLogin().loginPage("/login").loginProcessingUrl("/login.do").defaultSuccessUrl("/user/info") +// .failureUrl("/login?err=1") +// .permitAll() + .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessUrl("/") + .permitAll() + + .and().addFilterBefore(sso(), BasicAuthenticationFilter.class) + ; + + } + + private Filter sso() { + OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/github"); + OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(github(), oauth2ClientContext); + githubFilter.setRestTemplate(githubTemplate); + githubFilter.setTokenServices(new UserInfoTokenServices(githubResource().getUserInfoUri(), github().getClientId())); + return githubFilter; + } + + @Bean + @ConfigurationProperties("github.resource") + public ResourceServerProperties githubResource() { + return new ResourceServerProperties(); + } + + @Bean + @ConfigurationProperties("github.client") + public AuthorizationCodeResourceDetails github() { + return new AuthorizationCodeResourceDetails(); + } + + @Bean + public FilterRegistrationBean oauth2ClientFilterRegistration( + OAuth2ClientContextFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(filter); + registration.setOrder(-100); + return registration; + } +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/controller/LoginController.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/controller/LoginController.java new file mode 100644 index 00000000..b70ed7ea --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/controller/LoginController.java @@ -0,0 +1,18 @@ +package com.github.oauth.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class LoginController { + + @RequestMapping("/login") + public String login(String err, ModelMap modelMap) { + if (StringUtils.hasLength(err)) { + modelMap.put("err", err); + } + return "login"; + } +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/controller/PublicController.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/controller/PublicController.java new file mode 100644 index 00000000..6d56bf70 --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/controller/PublicController.java @@ -0,0 +1,14 @@ +package com.github.oauth.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class PublicController { + + @RequestMapping("/") + public String index() { + + return "index"; + } +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/IUserService.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/IUserService.java new file mode 100644 index 00000000..c6903397 --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/IUserService.java @@ -0,0 +1,6 @@ +package com.github.oauth.user; + +import com.github.oauth.common.ICommonService; + +public interface IUserService extends ICommonService { +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/User.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/User.java new file mode 100644 index 00000000..bb1afa38 --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/User.java @@ -0,0 +1,86 @@ +package com.github.oauth.user; + +import org.hibernate.annotations.DynamicUpdate; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; + +@Entity +@DynamicUpdate +public class User implements Serializable, UserDetails { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String username; + + private String password; + + private String role; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + public void setUsername(String username) { + this.username = username; + } + + @Override + public Collection getAuthorities() { + return Arrays.asList(new SimpleGrantedAuthority(getRole())); + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/UserController.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/UserController.java new file mode 100644 index 00000000..1ea9c44c --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/UserController.java @@ -0,0 +1,20 @@ +package com.github.oauth.user; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.security.Principal; + +@Controller +@RequestMapping("/user") +public class UserController { + + @RequestMapping("/info") + public String info(Principal principal, ModelMap modelMap){ + String name = principal.getName(); + modelMap.put("name", name); + + return "user/info"; + } +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/UserRepo.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/UserRepo.java new file mode 100644 index 00000000..dccf405b --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/UserRepo.java @@ -0,0 +1,7 @@ +package com.github.oauth.user; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface UserRepo extends JpaRepository, JpaSpecificationExecutor { +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/UserService.java b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/UserService.java new file mode 100644 index 00000000..5c07c15f --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/java/com/github/oauth/user/UserService.java @@ -0,0 +1,95 @@ +package com.github.oauth.user; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import javax.persistence.criteria.Predicate; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class UserService implements IUserService, UserDetailsService { + + @Autowired + private UserRepo userRepo; + + @Override + public User save(User entity) throws Exception { + return userRepo.save(entity); + } + + @Override + public void delete(Long id) throws Exception { + userRepo.delete(id); + } + + @Override + public void delete(User entity) throws Exception { + userRepo.delete(entity); + } + + @Override + public User findById(Long id) { + return userRepo.findOne(id); + } + + @Override + public User findBySample(User sample) { + return userRepo.findOne(whereSpec(sample)); + } + + @Override + public List findAll() { + return userRepo.findAll(); + } + + @Override + public List findAll(User sample) { + return userRepo.findAll(whereSpec(sample)); + } + + @Override + public Page findAll(PageRequest pageRequest) { + return userRepo.findAll(pageRequest); + } + + @Override + public Page findAll(User sample, PageRequest pageRequest) { + return userRepo.findAll(whereSpec(sample), pageRequest); + } + + private Specification whereSpec(final User sample){ + return (root, query, cb) -> { + List predicates = new ArrayList<>(); + if (sample.getId()!=null){ + predicates.add(cb.equal(root.get("id"), sample.getId())); + } + + if (StringUtils.hasLength(sample.getUsername())){ + predicates.add(cb.equal(root.get("username"),sample.getUsername())); + } + + return cb.and(predicates.toArray(new Predicate[predicates.size()])); + }; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User sample = new User(); + sample.setUsername(username); + User user = findBySample(sample); + + if( user == null ){ + throw new UsernameNotFoundException(String.format("User with username=%s was not found", username)); + } + + return user; + } +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/resources/application.properties b/quick-oauth2/quick-github-oauth/src/main/resources/application.properties new file mode 100644 index 00000000..b6b03a8c --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/resources/application.properties @@ -0,0 +1,41 @@ +server.port=8080 + +spring.thymeleaf.prefix=classpath:/templates/ +spring.thymeleaf.suffix=.html +spring.thymeleaf.mode=HTML5 +spring.thymeleaf.encoding=UTF-8 +# ;charset= is added +spring.thymeleaf.content-type=text/html +# set to false for hot refresh +spring.thymeleaf.cache=false + +#ݿ +spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false +#û +spring.datasource.username=root +# +spring.datasource.password=root123 +#ݿ +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +#Զ +spring.jpa.properties.hibernate.hbm2ddl.auto=update + +spring.jpa.generate-ddl=true +spring.jpa.hibernate.ddl-auto=update + +spring.messages.fallback-to-system-locale=false + +security.basic.enabled=false + +logging.level.org.springframework.security=debug + + + + +github.client.clientId=c5a0e4c011fb479ad95d +github.client.clientSecret=6c24ca098758a354cf952171392466d51518daf9 +github.client.accessTokenUri=https://github.com/login/oauth/access_token +github.client.userAuthorizationUri=https://github.com/login/oauth/authorize +github.client.authenticationScheme=query +github.client.clientAuthenticationScheme=form +github.resource.userInfoUri=https://api.github.com/user diff --git a/quick-oauth2/quick-github-oauth/src/main/resources/templates/index.html b/quick-oauth2/quick-github-oauth/src/main/resources/templates/index.html new file mode 100644 index 00000000..601959ba --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/resources/templates/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + Magneto + + + +
+ + {{message}} +
+ + + \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/resources/templates/login.html b/quick-oauth2/quick-github-oauth/src/main/resources/templates/login.html new file mode 100644 index 00000000..778fee3b --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/resources/templates/login.html @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/resources/templates/user/info.html b/quick-oauth2/quick-github-oauth/src/main/resources/templates/user/info.html new file mode 100644 index 00000000..d1ffdd40 --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/resources/templates/user/info.html @@ -0,0 +1,11 @@ + + + + + +
+ Welcome, + +
+ + \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/webapp/css/main.css b/quick-oauth2/quick-github-oauth/src/main/webapp/css/main.css new file mode 100644 index 00000000..588594c8 --- /dev/null +++ b/quick-oauth2/quick-github-oauth/src/main/webapp/css/main.css @@ -0,0 +1,6 @@ +.login { + height: 600px; + display: flex; + align-items: center; + justify-content: center; +} \ No newline at end of file diff --git a/quick-oauth2/quick-github-oauth/src/main/webapp/images/GitHub_Logo.png b/quick-oauth2/quick-github-oauth/src/main/webapp/images/GitHub_Logo.png new file mode 100644 index 00000000..e03d8dd8 Binary files /dev/null and b/quick-oauth2/quick-github-oauth/src/main/webapp/images/GitHub_Logo.png differ diff --git a/quick-oauth2/quick-oauth2-server/pom.xml b/quick-oauth2/quick-oauth2-server/pom.xml index baaf6965..45fb42b5 100644 --- a/quick-oauth2/quick-oauth2-server/pom.xml +++ b/quick-oauth2/quick-oauth2-server/pom.xml @@ -32,7 +32,7 @@
org.springframework.boot - spring-boot-starter-redis + spring-boot-starter-data-redis org.springframework.security diff --git a/quick-oauth2/quick-oauth2-server/src/main/java/com/quick/auth/server/security/AuthorizationServerConfiguration.java b/quick-oauth2/quick-oauth2-server/src/main/java/com/quick/auth/server/security/AuthorizationServerConfiguration.java index a2763fd5..71727093 100644 --- a/quick-oauth2/quick-oauth2-server/src/main/java/com/quick/auth/server/security/AuthorizationServerConfiguration.java +++ b/quick-oauth2/quick-oauth2-server/src/main/java/com/quick/auth/server/security/AuthorizationServerConfiguration.java @@ -5,25 +5,15 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.support.collections.RedisStore; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; -import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; -import java.util.HashMap; -import java.util.Map; - /** * 认证服务器配置 */ @@ -31,93 +21,57 @@ @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { - @Value("${resource.id:spring-boot-application}") // 默认值spring-boot-application - private String resourceId; - - @Value("${access_token.validity_period:3600}") // 默认值3600 - int accessTokenValiditySeconds = 3600; - - @Autowired - private AuthenticationManager authenticationManager; - - @Autowired - private RedisConnectionFactory connectionFactory; + @Value("${resource.id:spring-boot-application}") // 默认值spring-boot-application + private String resourceId; - @Override - public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { - endpoints.authenticationManager(this.authenticationManager); - endpoints.accessTokenConverter(accessTokenConverter()); - endpoints.tokenStore(tokenStore()); - } + @Value("${access_token.validity_period:3600}") // 默认值3600 + int accessTokenValiditySeconds = 3600; - @Override - public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { - oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')"); - oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')"); - } + @Autowired + private AuthenticationManager authenticationManager; - @Override - public void configure(ClientDetailsServiceConfigurer clients) throws Exception { - clients.inMemory() - .withClient("normal-app") - .authorizedGrantTypes("authorization_code", "implicit") - .authorities("ROLE_CLIENT") - .scopes("read", "write") - .resourceIds(resourceId) - .accessTokenValiditySeconds(accessTokenValiditySeconds) - .and() - .withClient("trusted-app") - .authorizedGrantTypes("client_credentials", "password") - .authorities("ROLE_TRUSTED_CLIENT") - .scopes("read", "write") - .resourceIds(resourceId) - .accessTokenValiditySeconds(accessTokenValiditySeconds) - .secret("secret"); - } + @Autowired + private RedisConnectionFactory redisConnectionFactory; -// @Bean -// public RedisTokenStore tokenStore() { -// return new RedisTokenStore(connectionFactory); -// } + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.authenticationManager(this.authenticationManager); + endpoints.tokenStore(tokenStore()); + } - /** - * token converter - * - * @return - */ - @Bean - public JwtAccessTokenConverter accessTokenConverter() { - JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() { - /*** - * 重写增强token方法,用于自定义一些token返回的信息 - */ - @Override - public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { - String userName = authentication.getUserAuthentication().getName(); - User user = (User) authentication.getUserAuthentication().getPrincipal();// 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration} - /** 自定义一些token属性 ***/ - final Map additionalInformation = new HashMap<>(); - additionalInformation.put("userName", userName); - additionalInformation.put("roles", user.getAuthorities()); - ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation); - OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication); - return enhancedToken; - } + @Override + public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { + oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')"); + oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')"); + } - }; - accessTokenConverter.setSigningKey("123");// 测试用,资源服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式 - return accessTokenConverter; + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.inMemory() + .withClient("normal-app") + .authorizedGrantTypes("authorization_code", "implicit") + .authorities("ROLE_CLIENT") + .scopes("read", "write") + .resourceIds(resourceId) + .accessTokenValiditySeconds(accessTokenValiditySeconds) + .and() + .withClient("trusted-app") + .authorizedGrantTypes("client_credentials", "password") + .authorities("ROLE_TRUSTED_CLIENT") + .scopes("read", "write") + .resourceIds(resourceId) + .accessTokenValiditySeconds(accessTokenValiditySeconds) + .secret("secret"); } + /** * token store * - * @param accessTokenConverter * @return */ @Bean public TokenStore tokenStore() { - TokenStore tokenStore = new JwtTokenStore(accessTokenConverter()); - return tokenStore; + return new RedisTokenStore(redisConnectionFactory); } } \ No newline at end of file diff --git a/quick-oauth2/quick-oauth2-server/src/main/resources/application.properties b/quick-oauth2/quick-oauth2-server/src/main/resources/application.properties index c1110023..3215fb8d 100644 --- a/quick-oauth2/quick-oauth2-server/src/main/resources/application.properties +++ b/quick-oauth2/quick-oauth2-server/src/main/resources/application.properties @@ -3,34 +3,20 @@ server.port=1130 #spring.jpa.show-sql=true spring.jpa.generate-ddl=true -#���ݿ����� -spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false -#�û��� +spring.datasource.url=jdbc:mysql://localhost:3308/test?useUnicode=true&characterEncoding=utf-8&useSSL=false spring.datasource.username=root -#���� -spring.datasource.password=root -#���ݿ����� +spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver -#�Զ����� spring.jpa.properties.hibernate.hbm2ddl.auto=update # REDIS (RedisProperties) -# Redis���ݿ�������Ĭ��Ϊ0�� spring.redis.database=1 -# Redis��������ַ spring.redis.host=localhost -# Redis���������Ӷ˿� spring.redis.port=6379 -# Redis�������������루Ĭ��Ϊ�գ� spring.redis.password= -# ���ӳ������������ʹ�ø�ֵ��ʾû�����ƣ� spring.redis.pool.max-active=8 -# ���ӳ���������ȴ�ʱ�䣨ʹ�ø�ֵ��ʾû�����ƣ� spring.redis.pool.max-wait=-1 -# ���ӳ��е����������� spring.redis.pool.max-idle=8 -# ���ӳ��е���С�������� spring.redis.pool.min-idle=0 -# ���ӳ�ʱʱ�䣨���룩 spring.redis.timeout=0 \ No newline at end of file diff --git a/quick-okhttp/pom.xml b/quick-okhttp/pom.xml index 3586f057..322773b8 100644 --- a/quick-okhttp/pom.xml +++ b/quick-okhttp/pom.xml @@ -3,12 +3,16 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> quick-okhttp - - - com.quick 1.0-SNAPSHOT 4.0.0 + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + @@ -24,32 +28,27 @@ - com.squareup.okhttp + com.squareup.okhttp3 okhttp - 2.5.0 com.google.guava guava - 19.0-rc2 com.alibaba fastjson - 1.2.7 commons-io commons-io - 2.4 org.apache.commons commons-lang3 - 3.4 diff --git a/quick-okhttp/src/main/java/com/quick/okhttp/PostString.java b/quick-okhttp/src/main/java/com/quick/okhttp/PostString.java index 3b226259..4686f672 100644 --- a/quick-okhttp/src/main/java/com/quick/okhttp/PostString.java +++ b/quick-okhttp/src/main/java/com/quick/okhttp/PostString.java @@ -7,11 +7,12 @@ public class PostString { public static void main(String[] args) throws IOException { OkHttpClient client = new OkHttpClient(); - MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain"); - String postBody = "Hello World"; + MediaType MEDIA_TYPE_TEXT = MediaType.parse("application/json"); + String postBody = "{\"username\":\"3\",\"password\":\"string\",\"email\":\"string\"}"; Request request = new Request.Builder() - .url("http://www.baidu.com") + .url("http://localhost:54321/users/signup") +// .header("Content-Type","application/json") .post(RequestBody.create(MEDIA_TYPE_TEXT, postBody)) .build(); diff --git a/quick-okhttp/src/main/java/com/quick/okhttp/SyncGet.java b/quick-okhttp/src/main/java/com/quick/okhttp/SyncGet.java index 67d7fdf7..8385fb23 100644 --- a/quick-okhttp/src/main/java/com/quick/okhttp/SyncGet.java +++ b/quick-okhttp/src/main/java/com/quick/okhttp/SyncGet.java @@ -9,36 +9,25 @@ import java.util.List; public class SyncGet { - public static void main(String[] args) throws IOException { - OkHttpClient client = new OkHttpClient(); - client.interceptors().add(new LoggingInterceptor()); - String[] split = {"b647a889-8962-11e7-b8af-70106fb01274", "b66ac9da-8962-11e7-b8af-70106fb01274", "b66194fb-8962-11e7-b8af-70106fb01274", "b6479575-8962-11e7-b8af-70106fb01274", "b66bfcb0-8962-11e7-b8af-70106fb01274", "b64f5539-8962-11e7-b8af-70106fb01274", "b6677695-8962-11e7-b8af-70106fb01274", "b6464f70-8962-11e7-b8af-70106fb01274", "b66da7df-8962-11e7-b8af-70106fb01274", "b65ccd2e-8962-11e7-b8af-70106fb01274", "b6455118-8962-11e7-b8af-70106fb01274", "b657c92b-8962-11e7-b8af-70106fb01274", "b66e36ad-8962-11e7-b8af-70106fb01274", "b646311b-8962-11e7-b8af-70106fb01274", "b66a85c4-8962-11e7-b8af-70106fb01274", "b659e8ea-8962-11e7-b8af-70106fb01274", "b661dd2c-8962-11e7-b8af-70106fb01274", "b669d3b4-8962-11e7-b8af-70106fb01274", "b66df932-8962-11e7-b8af-70106fb01274", "b65d938b-8962-11e7-b8af-70106fb01274", "b660a430-8962-11e7-b8af-70106fb01274", "b646a8a7-8962-11e7-b8af-70106fb01274", "b650af93-8962-11e7-b8af-70106fb01274", "b66a8731-8962-11e7-b8af-70106fb01274", "b646fce8-8962-11e7-b8af-70106fb01274", "b6684a79-8962-11e7-b8af-70106fb01274", "b66c5db7-8962-11e7-b8af-70106fb01274", "b645e45d-8962-11e7-b8af-70106fb01274", "b645ef47-8962-11e7-b8af-70106fb01274", "b6608f57-8962-11e7-b8af-70106fb01274", "b65eb65f-8962-11e7-b8af-70106fb01274", "b66a3ded-8962-11e7-b8af-70106fb01274", "b669bc40-8962-11e7-b8af-70106fb01274", "b65fd48b-8962-11e7-b8af-70106fb01274", "b6647917-8962-11e7-b8af-70106fb01274", "b6476eaa-8962-11e7-b8af-70106fb01274", "b6697fc4-8962-11e7-b8af-70106fb01274", "b64e561d-8962-11e7-b8af-70106fb01274", "b66db82d-8962-11e7-b8af-70106fb01274", "b645e855-8962-11e7-b8af-70106fb01274", "b647974d-8962-11e7-b8af-70106fb01274", "b6469df6-8962-11e7-b8af-70106fb01274", "b668c7b3-8962-11e7-b8af-70106fb01274", "b646c510-8962-11e7-b8af-70106fb01274", "b65d5447-8962-11e7-b8af-70106fb01274", "b64e665f-8962-11e7-b8af-70106fb01274", "b647a271-8962-11e7-b8af-70106fb01274", "b663a6fc-8962-11e7-b8af-70106fb01274", "b64e2acb-8962-11e7-b8af-70106fb01274", "b6467e98-8962-11e7-b8af-70106fb01274", "b6524183-8962-11e7-b8af-70106fb01274", "b664c2fe-8962-11e7-b8af-70106fb01274", "b64e1c5f-8962-11e7-b8af-70106fb01274", "b658509b-8962-11e7-b8af-70106fb01274", "b656ae48-8962-11e7-b8af-70106fb01274", "b65e255d-8962-11e7-b8af-70106fb01274", "b661b5b9-8962-11e7-b8af-70106fb01274", "b65947d5-8962-11e7-b8af-70106fb01274", "b666ba77-8962-11e7-b8af-70106fb01274", "b65e8571-8962-11e7-b8af-70106fb01274", "b66b3b4a-8962-11e7-b8af-70106fb01274", "b6607625-8962-11e7-b8af-70106fb01274", "b66361c4-8962-11e7-b8af-70106fb01274", "b6634309-8962-11e7-b8af-70106fb01274", "b646494d-8962-11e7-b8af-70106fb01274", "b6523a85-8962-11e7-b8af-70106fb01274", "b66916fa-8962-11e7-b8af-70106fb01274", "b647706f-8962-11e7-b8af-70106fb01274", "b64db5dd-8962-11e7-b8af-70106fb01274", "a7dd02f5-77c1-4bec-b474-d1751f5eebd7", "b65bb1b4-8962-11e7-b8af-70106fb01274", "b64689f9-8962-11e7-b8af-70106fb01274", "b65d6e77-8962-11e7-b8af-70106fb01274", "b64f81bc-8962-11e7-b8af-70106fb01274", "b668d27c-8962-11e7-b8af-70106fb01274", "b6566cf9-8962-11e7-b8af-70106fb01274", "b666e077-8962-11e7-b8af-70106fb01274", "b66911f6-8962-11e7-b8af-70106fb01274", "b646745f-8962-11e7-b8af-70106fb01274", "a7df042e-84ef-4884-8d07-1c75afbfa344", "b6529647-8962-11e7-b8af-70106fb01274", "b665df82-8962-11e7-b8af-70106fb01274", "b6509dae-8962-11e7-b8af-70106fb01274", "b669d668-8962-11e7-b8af-70106fb01274", "b65fcc55-8962-11e7-b8af-70106fb01274", "b65baaae-8962-11e7-b8af-70106fb01274", "b66e74a9-8962-11e7-b8af-70106fb01274", "b666c5de-8962-11e7-b8af-70106fb01274", "b66879fd-8962-11e7-b8af-70106fb01274", "b653d655-8962-11e7-b8af-70106fb01274", "b660989c-8962-11e7-b8af-70106fb01274", "b64642d8-8962-11e7-b8af-70106fb01274", "b6568bcf-8962-11e7-b8af-70106fb01274", "b64ea589-8962-11e7-b8af-70106fb01274", "b669c07d-8962-11e7-b8af-70106fb01274", "b6530199-8962-11e7-b8af-70106fb01274", "b66e4155-8962-11e7-b8af-70106fb01274", "b645e679-8962-11e7-b8af-70106fb01274", "b6503d5e-8962-11e7-b8af-70106fb01274", "b66a1331-8962-11e7-b8af-70106fb01274", "b64575f6-8962-11e7-b8af-70106fb01274", "b657d399-8962-11e7-b8af-70106fb01274", "b65ae2a7-8962-11e7-b8af-70106fb01274", "b6606622-8962-11e7-b8af-70106fb01274", "b66a996a-8962-11e7-b8af-70106fb01274", "b64f8864-8962-11e7-b8af-70106fb01274", "b6560968-8962-11e7-b8af-70106fb01274", "b66c8f0a-8962-11e7-b8af-70106fb01274", "b664cf9e-8962-11e7-b8af-70106fb01274", "b6576bf6-8962-11e7-b8af-70106fb01274", "a7f1037d-aca8-403c-9b4e-d57018e495ca", "b6527f5e-8962-11e7-b8af-70106fb01274", "b6631977-8962-11e7-b8af-70106fb01274", "b6597653-8962-11e7-b8af-70106fb01274", "b647a568-8962-11e7-b8af-70106fb01274", "b657a254-8962-11e7-b8af-70106fb01274", "b66666bb-8962-11e7-b8af-70106fb01274", "b66620d5-8962-11e7-b8af-70106fb01274", "b64695c4-8962-11e7-b8af-70106fb01274", "b64f36bc-8962-11e7-b8af-70106fb01274", "b65c7bd9-8962-11e7-b8af-70106fb01274", "b652b119-8962-11e7-b8af-70106fb01274", "b64752c7-8962-11e7-b8af-70106fb01274", "b66074a1-8962-11e7-b8af-70106fb01274", "b65727d1-8962-11e7-b8af-70106fb01274", "b65d0a8e-8962-11e7-b8af-70106fb01274", "b6513bee-8962-11e7-b8af-70106fb01274", "b6629443-8962-11e7-b8af-70106fb01274", "b6455704-8962-11e7-b8af-70106fb01274", "b64e3d6d-8962-11e7-b8af-70106fb01274", "b6698444-8962-11e7-b8af-70106fb01274", "b6666471-8962-11e7-b8af-70106fb01274", "b6688574-8962-11e7-b8af-70106fb01274", "b6459e06-8962-11e7-b8af-70106fb01274", "b66b0dee-8962-11e7-b8af-70106fb01274", "b65ef2e7-8962-11e7-b8af-70106fb01274", "b666c2c9-8962-11e7-b8af-70106fb01274", "b667496b-8962-11e7-b8af-70106fb01274", "b66926ba-8962-11e7-b8af-70106fb01274", "b6457cf9-8962-11e7-b8af-70106fb01274", "b659e070-8962-11e7-b8af-70106fb01274", "b66a7cfc-8962-11e7-b8af-70106fb01274", "b656b265-8962-11e7-b8af-70106fb01274", "b661f050-8962-11e7-b8af-70106fb01274", "b64e8bd4-8962-11e7-b8af-70106fb01274", "b650964a-8962-11e7-b8af-70106fb01274", "b64ef6db-8962-11e7-b8af-70106fb01274", "b64694fd-8962-11e7-b8af-70106fb01274", "b66e609f-8962-11e7-b8af-70106fb01274", "b652a883-8962-11e7-b8af-70106fb01274", "b6532eb5-8962-11e7-b8af-70106fb01274", "b646df47-8962-11e7-b8af-70106fb01274", "b6566f57-8962-11e7-b8af-70106fb01274", "b668ddcb-8962-11e7-b8af-70106fb01274", "b65fd544-8962-11e7-b8af-70106fb01274", "b65be73b-8962-11e7-b8af-70106fb01274", "b665fa70-8962-11e7-b8af-70106fb01274", "a7f2035c-6e77-40a2-ba0d-ebe86f44d4a6", "b65a0240-8962-11e7-b8af-70106fb01274", "b658cc1e-8962-11e7-b8af-70106fb01274", "a7e503f8-42f5-4243-a781-77ae3e69f53a", "b65a089d-8962-11e7-b8af-70106fb01274", "b668e8b9-8962-11e7-b8af-70106fb01274", "b65984c6-8962-11e7-b8af-70106fb01274", "b65b180b-8962-11e7-b8af-70106fb01274", "b6669b6d-8962-11e7-b8af-70106fb01274", "b65254ab-8962-11e7-b8af-70106fb01274", "b66c822c-8962-11e7-b8af-70106fb01274", "b66305da-8962-11e7-b8af-70106fb01274", "b660f305-8962-11e7-b8af-70106fb01274", "b646e146-8962-11e7-b8af-70106fb01274", "b668a342-8962-11e7-b8af-70106fb01274", "b6467556-8962-11e7-b8af-70106fb01274", "b66d3d08-8962-11e7-b8af-70106fb01274", "b6463af8-8962-11e7-b8af-70106fb01274", "b65166b5-8962-11e7-b8af-70106fb01274", "b66abac1-8962-11e7-b8af-70106fb01274", "b6464c27-8962-11e7-b8af-70106fb01274", "b6585381-8962-11e7-b8af-70106fb01274", "b646d01f-8962-11e7-b8af-70106fb01274", "b66b9331-8962-11e7-b8af-70106fb01274", "b64731a3-8962-11e7-b8af-70106fb01274", "b669058a-8962-11e7-b8af-70106fb01274", "b650252f-8962-11e7-b8af-70106fb01274", "b64db930-8962-11e7-b8af-70106fb01274", "b664a1c0-8962-11e7-b8af-70106fb01274", "b647055b-8962-11e7-b8af-70106fb01274", "b64dc7ef-8962-11e7-b8af-70106fb01274", "a7ed039a-6705-4496-99c0-d9e1d18ae41b", "b66646ae-8962-11e7-b8af-70106fb01274", "b6680899-8962-11e7-b8af-70106fb01274", "b65ebe3e-8962-11e7-b8af-70106fb01274", "b65cae5f-8962-11e7-b8af-70106fb01274", "b659acad-8962-11e7-b8af-70106fb01274", "b66e4f52-8962-11e7-b8af-70106fb01274", "b65d412b-8962-11e7-b8af-70106fb01274", "b64e1a49-8962-11e7-b8af-70106fb01274", "b64713e5-8962-11e7-b8af-70106fb01274", "b6468871-8962-11e7-b8af-70106fb01274", "b645adb6-8962-11e7-b8af-70106fb01274", "b6458bfa-8962-11e7-b8af-70106fb01274", "b6670159-8962-11e7-b8af-70106fb01274", "b65e5074-8962-11e7-b8af-70106fb01274", "b64632e4-8962-11e7-b8af-70106fb01274", "b646df88-8962-11e7-b8af-70106fb01274", "b6596876-8962-11e7-b8af-70106fb01274", "b661a2f0-8962-11e7-b8af-70106fb01274", "b663e453-8962-11e7-b8af-70106fb01274", "b6610a5b-8962-11e7-b8af-70106fb01274", "b6565211-8962-11e7-b8af-70106fb01274", "b6464bb9-8962-11e7-b8af-70106fb01274", "b65bf6e6-8962-11e7-b8af-70106fb01274", "b646e20d-8962-11e7-b8af-70106fb01274", "b66e50ca-8962-11e7-b8af-70106fb01274", "b6694a49-8962-11e7-b8af-70106fb01274", "b6465827-8962-11e7-b8af-70106fb01274", "b64e53c0-8962-11e7-b8af-70106fb01274", "b668abc5-8962-11e7-b8af-70106fb01274", "b66af334-8962-11e7-b8af-70106fb01274", "b65dc977-8962-11e7-b8af-70106fb01274", "b66a8e93-8962-11e7-b8af-70106fb01274", "b662529b-8962-11e7-b8af-70106fb01274", "b65fea77-8962-11e7-b8af-70106fb01274", "b65e51a2-8962-11e7-b8af-70106fb01274", "b6506a66-8962-11e7-b8af-70106fb01274", "b66b30c1-8962-11e7-b8af-70106fb01274", "b66e5d5e-8962-11e7-b8af-70106fb01274", "b64ed7b9-8962-11e7-b8af-70106fb01274", "b65d4988-8962-11e7-b8af-70106fb01274", "b64774a9-8962-11e7-b8af-70106fb01274", "b658b923-8962-11e7-b8af-70106fb01274", "b666b00f-8962-11e7-b8af-70106fb01274", "b66b66a4-8962-11e7-b8af-70106fb01274", "b659d4ac-8962-11e7-b8af-70106fb01274", "b66bc164-8962-11e7-b8af-70106fb01274", "b65da345-8962-11e7-b8af-70106fb01274", "b64dec58-8962-11e7-b8af-70106fb01274", "b6624002-8962-11e7-b8af-70106fb01274", " ", "b66a32fc-8962-11e7-b8af-70106fb01274", "b659c841-8962-11e7-b8af-70106fb01274", "b647a635-8962-11e7-b8af-70106fb01274", "b646f7a5-8962-11e7-b8af-70106fb01274", "b651ad6b-8962-11e7-b8af-70106fb01274", "b65ecf43-8962-11e7-b8af-70106fb01274", "b65eb9cc-8962-11e7-b8af-70106fb01274", "b6471983-8962-11e7-b8af-70106fb01274", "b64723ed-8962-11e7-b8af-70106fb01274", "b64565f1-8962-11e7-b8af-70106fb01274", "b65e1147-8962-11e7-b8af-70106fb01274", "b6573813-8962-11e7-b8af-70106fb01274", "b66b8c32-8962-11e7-b8af-70106fb01274", "b65a579b-8962-11e7-b8af-70106fb01274", "b6578d02-8962-11e7-b8af-70106fb01274", "b666dd88-8962-11e7-b8af-70106fb01274", "b6634c21-8962-11e7-b8af-70106fb01274", "b646f408-8962-11e7-b8af-70106fb01274", "b66df975-8962-11e7-b8af-70106fb01274", "b65da391-8962-11e7-b8af-70106fb01274", "b661b579-8962-11e7-b8af-70106fb01274", "b6464565-8962-11e7-b8af-70106fb01274", "b64d9872-8962-11e7-b8af-70106fb01274", "b647a8ef-8962-11e7-b8af-70106fb01274", "b65bca61-8962-11e7-b8af-70106fb01274", "b66c1126-8962-11e7-b8af-70106fb01274", "b65a9620-8962-11e7-b8af-70106fb01274", "b65d0533-8962-11e7-b8af-70106fb01274", "b65e05ec-8962-11e7-b8af-70106fb01274", "b6602f57-8962-11e7-b8af-70106fb01274", "b64548b4-8962-11e7-b8af-70106fb01274", "b65dfa96-8962-11e7-b8af-70106fb01274", "b664dca4-8962-11e7-b8af-70106fb01274", "b661b7db-8962-11e7-b8af-70106fb01274", "b65bb83c-8962-11e7-b8af-70106fb01274", "b6470ea9-8962-11e7-b8af-70106fb01274", "b665812c-8962-11e7-b8af-70106fb01274", "b66a83a4-8962-11e7-b8af-70106fb01274", "b6611327-8962-11e7-b8af-70106fb01274", "b669cb6f-8962-11e7-b8af-70106fb01274", "b65b5a30-8962-11e7-b8af-70106fb01274", "b6467e17-8962-11e7-b8af-70106fb01274", "b6593736-8962-11e7-b8af-70106fb01274", "b64e6c11-8962-11e7-b8af-70106fb01274", "b66b5c4e-8962-11e7-b8af-70106fb01274", "b6522997-8962-11e7-b8af-70106fb01274", "b64f5c8c-8962-11e7-b8af-70106fb01274", "b657ba98-8962-11e7-b8af-70106fb01274", "b645795a-8962-11e7-b8af-70106fb01274", "b65b8bd3-8962-11e7-b8af-70106fb01274", "b6697af2-8962-11e7-b8af-70106fb01274", "b66d2e66-8962-11e7-b8af-70106fb01274", "b66822ae-8962-11e7-b8af-70106fb01274", "b66121fc-8962-11e7-b8af-70106fb01274", "b64fc74e-8962-11e7-b8af-70106fb01274", "b65015a2-8962-11e7-b8af-70106fb01274", "b658f997-8962-11e7-b8af-70106fb01274", "b652850b-8962-11e7-b8af-70106fb01274", "b65f0070-8962-11e7-b8af-70106fb01274", "b6463ecf-8962-11e7-b8af-70106fb01274", "b666d57b-8962-11e7-b8af-70106fb01274", "b64e50b2-8962-11e7-b8af-70106fb01274", "b658c261-8962-11e7-b8af-70106fb01274", "b646a8ff-8962-11e7-b8af-70106fb01274", "b6515859-8962-11e7-b8af-70106fb01274", "b64db05c-8962-11e7-b8af-70106fb01274", "b64e63b3-8962-11e7-b8af-70106fb01274", "b64e6030-8962-11e7-b8af-70106fb01274", "b661cb47-8962-11e7-b8af-70106fb01274", "b6596ceb-8962-11e7-b8af-70106fb01274", "b65a25b9-8962-11e7-b8af-70106fb01274", "b645c336-8962-11e7-b8af-70106fb01274", "b660c474-8962-11e7-b8af-70106fb01274", "b66a44b6-8962-11e7-b8af-70106fb01274", "b669696f-8962-11e7-b8af-70106fb01274", "b664cec5-8962-11e7-b8af-70106fb01274", "b6649e56-8962-11e7-b8af-70106fb01274", "b66e0cd1-8962-11e7-b8af-70106fb01274", "b66a74c8-8962-11e7-b8af-70106fb01274", "b6474a99-8962-11e7-b8af-70106fb01274", "b646d09a-8962-11e7-b8af-70106fb01274", "b65eb857-8962-11e7-b8af-70106fb01274", "b65cd604-8962-11e7-b8af-70106fb01274", "b66c5cfc-8962-11e7-b8af-70106fb01274", "b66bee87-8962-11e7-b8af-70106fb01274", "b65ff2bb-8962-11e7-b8af-70106fb01274", "b65f17e5-8962-11e7-b8af-70106fb01274", "b66abf28-8962-11e7-b8af-70106fb01274", "b65031ee-8962-11e7-b8af-70106fb01274", "b65e6891-8962-11e7-b8af-70106fb01274", "b6454cde-8962-11e7-b8af-70106fb01274", "b6519a33-8962-11e7-b8af-70106fb01274", "b657fb72-8962-11e7-b8af-70106fb01274", "b6649819-8962-11e7-b8af-70106fb01274", "b6613bbb-8962-11e7-b8af-70106fb01274", "b663dbf3-8962-11e7-b8af-70106fb01274", "b6601d60-8962-11e7-b8af-70106fb01274", "b6632048-8962-11e7-b8af-70106fb01274", "b647233d-8962-11e7-b8af-70106fb01274", "b6636d11-8962-11e7-b8af-70106fb01274", "b659d133-8962-11e7-b8af-70106fb01274", "b666fb4a-8962-11e7-b8af-70106fb01274", "b660a624-8962-11e7-b8af-70106fb01274", "b6637cf1-8962-11e7-b8af-70106fb01274", "b65bde2d-8962-11e7-b8af-70106fb01274", "b6623e12-8962-11e7-b8af-70106fb01274", "b6467d6c-8962-11e7-b8af-70106fb01274", "b64f5792-8962-11e7-b8af-70106fb01274", "b657072f-8962-11e7-b8af-70106fb01274", "b647731d-8962-11e7-b8af-70106fb01274", "b66af7df-8962-11e7-b8af-70106fb01274", "b6527341-8962-11e7-b8af-70106fb01274", "b65a5832-8962-11e7-b8af-70106fb01274", "b65bbdea-8962-11e7-b8af-70106fb01274", "b645b955-8962-11e7-b8af-70106fb01274", "b66e6acf-8962-11e7-b8af-70106fb01274", "b664bf38-8962-11e7-b8af-70106fb01274", "b6588990-8962-11e7-b8af-70106fb01274", "b66b3e18-8962-11e7-b8af-70106fb01274", "b65d6d38-8962-11e7-b8af-70106fb01274", "b6457759-8962-11e7-b8af-70106fb01274", "b66a7e0d-8962-11e7-b8af-70106fb01274", "b65a958e-8962-11e7-b8af-70106fb01274", "b65d2b9b-8962-11e7-b8af-70106fb01274", "b659f583-8962-11e7-b8af-70106fb01274", "b66763c7-8962-11e7-b8af-70106fb01274", "b65be706-8962-11e7-b8af-70106fb01274", "b6457697-8962-11e7-b8af-70106fb01274", "b6461f20-8962-11e7-b8af-70106fb01274", "b646009a-8962-11e7-b8af-70106fb01274", "b66e096c-8962-11e7-b8af-70106fb01274", "b65fdadf-8962-11e7-b8af-70106fb01274", "b65aa76b-8962-11e7-b8af-70106fb01274", "b667052f-8962-11e7-b8af-70106fb01274", "b65f0705-8962-11e7-b8af-70106fb01274", "b659cac7-8962-11e7-b8af-70106fb01274", "b6470b4f-8962-11e7-b8af-70106fb01274", "b65a7f2f-8962-11e7-b8af-70106fb01274", "b64daba1-8962-11e7-b8af-70106fb01274", "b659f8bf-8962-11e7-b8af-70106fb01274", "b664a99e-8962-11e7-b8af-70106fb01274", "b667bad0-8962-11e7-b8af-70106fb01274", "b65a61ed-8962-11e7-b8af-70106fb01274", "b6513f7e-8962-11e7-b8af-70106fb01274", "b647263b-8962-11e7-b8af-70106fb01274", "b646f71c-8962-11e7-b8af-70106fb01274", "b647122a-8962-11e7-b8af-70106fb01274", "b6468595-8962-11e7-b8af-70106fb01274", "b6682aed-8962-11e7-b8af-70106fb01274", "b65fbf88-8962-11e7-b8af-70106fb01274", "b66c62c3-8962-11e7-b8af-70106fb01274", "b65c637b-8962-11e7-b8af-70106fb01274", "b6597597-8962-11e7-b8af-70106fb01274", "b6469284-8962-11e7-b8af-70106fb01274", "b651581d-8962-11e7-b8af-70106fb01274", "b65d4fbe-8962-11e7-b8af-70106fb01274", "b661692d-8962-11e7-b8af-70106fb01274", "b65acd30-8962-11e7-b8af-70106fb01274", "b6667adf-8962-11e7-b8af-70106fb01274", "b656600b-8962-11e7-b8af-70106fb01274", "b6676135-8962-11e7-b8af-70106fb01274", "b657f37f-8962-11e7-b8af-70106fb01274", "b6685969-8962-11e7-b8af-70106fb01274", "b66621fa-8962-11e7-b8af-70106fb01274", "b660ad1a-8962-11e7-b8af-70106fb01274", "b6675421-8962-11e7-b8af-70106fb01274", "b65841ea-8962-11e7-b8af-70106fb01274", "b64602de-8962-11e7-b8af-70106fb01274", "b6602f16-8962-11e7-b8af-70106fb01274", "b66e1901-8962-11e7-b8af-70106fb01274", "b6595575-8962-11e7-b8af-70106fb01274", "b6663c8d-8962-11e7-b8af-70106fb01274", "b6680b69-8962-11e7-b8af-70106fb01274", "b65a605b-8962-11e7-b8af-70106fb01274", "b6580bc3-8962-11e7-b8af-70106fb01274", "b669dd4c-8962-11e7-b8af-70106fb01274", "b65a749a-8962-11e7-b8af-70106fb01274", "b65070cb-8962-11e7-b8af-70106fb01274", "b66c8435-8962-11e7-b8af-70106fb01274", "b660659f-8962-11e7-b8af-70106fb01274", "b664724a-8962-11e7-b8af-70106fb01274", "b66793ae-8962-11e7-b8af-70106fb01274", "b65255b7-8962-11e7-b8af-70106fb01274", "b65137c1-8962-11e7-b8af-70106fb01274", "b65e2340-8962-11e7-b8af-70106fb01274", "b6501402-8962-11e7-b8af-70106fb01274", "b65e8116-8962-11e7-b8af-70106fb01274", "b6639d63-8962-11e7-b8af-70106fb01274", "b65e77c5-8962-11e7-b8af-70106fb01274", "b66234ad-8962-11e7-b8af-70106fb01274", "b65a0c77-8962-11e7-b8af-70106fb01274", "b65774f0-8962-11e7-b8af-70106fb01274", "b64fc5ef-8962-11e7-b8af-70106fb01274", "b66840e0-8962-11e7-b8af-70106fb01274", "b645dd5e-8962-11e7-b8af-70106fb01274", "b669f0cd-8962-11e7-b8af-70106fb01274", "b66a60ed-8962-11e7-b8af-70106fb01274", "b65fc539-8962-11e7-b8af-70106fb01274", "b6470b38-8962-11e7-b8af-70106fb01274", "b660b3d2-8962-11e7-b8af-70106fb01274", "b65a955d-8962-11e7-b8af-70106fb01274", "b663f65c-8962-11e7-b8af-70106fb01274", "b66cbccf-8962-11e7-b8af-70106fb01274", "b668b4c0-8962-11e7-b8af-70106fb01274", "b65dcac8-8962-11e7-b8af-70106fb01274", "b657c290-8962-11e7-b8af-70106fb01274", "b65b1a4f-8962-11e7-b8af-70106fb01274", "b6698d33-8962-11e7-b8af-70106fb01274", "b65bdfda-8962-11e7-b8af-70106fb01274", "b66cb2a3-8962-11e7-b8af-70106fb01274", "b66b0b30-8962-11e7-b8af-70106fb01274", "b663f23f-8962-11e7-b8af-70106fb01274", "b646f01e-8962-11e7-b8af-70106fb01274", "b66e49e1-8962-11e7-b8af-70106fb01274", "b664acf9-8962-11e7-b8af-70106fb01274", "b65dd9f8-8962-11e7-b8af-70106fb01274", "b661a67d-8962-11e7-b8af-70106fb01274", "a7ea02f0-48b4-4de9-863f-196a85f268e6", "b666c395-8962-11e7-b8af-70106fb01274", "b667d121-8962-11e7-b8af-70106fb01274", "b645f4cf-8962-11e7-b8af-70106fb01274", "b65e21db-8962-11e7-b8af-70106fb01274", "b64df6c4-8962-11e7-b8af-70106fb01274", "b6652baf-8962-11e7-b8af-70106fb01274", "b668b275-8962-11e7-b8af-70106fb01274", "b66703ae-8962-11e7-b8af-70106fb01274", "b64e03b2-8962-11e7-b8af-70106fb01274", "b668673c-8962-11e7-b8af-70106fb01274", "b65bcd6a-8962-11e7-b8af-70106fb01274", "b658bad9-8962-11e7-b8af-70106fb01274", "b6571178-8962-11e7-b8af-70106fb01274", "b646ff2b-8962-11e7-b8af-70106fb01274", "b6668644-8962-11e7-b8af-70106fb01274", "b65762b5-8962-11e7-b8af-70106fb01274", "b64de69b-8962-11e7-b8af-70106fb01274", "b646eef3-8962-11e7-b8af-70106fb01274", "b666ba22-8962-11e7-b8af-70106fb01274", "b665b207-8962-11e7-b8af-70106fb01274", "b66812d6-8962-11e7-b8af-70106fb01274", "b65d7369-8962-11e7-b8af-70106fb01274", "b66064a3-8962-11e7-b8af-70106fb01274", "b65ac999-8962-11e7-b8af-70106fb01274", "b661e140-8962-11e7-b8af-70106fb01274", "b6509cb5-8962-11e7-b8af-70106fb01274", "b646a664-8962-11e7-b8af-70106fb01274", "b666c200-8962-11e7-b8af-70106fb01274", "b645e487-8962-11e7-b8af-70106fb01274", "b658c4f7-8962-11e7-b8af-70106fb01274", "b645cd7a-8962-11e7-b8af-70106fb01274", "b652785c-8962-11e7-b8af-70106fb01274", "b6605654-8962-11e7-b8af-70106fb01274", "b666438c-8962-11e7-b8af-70106fb01274", "b65902f7-8962-11e7-b8af-70106fb01274", "b66c0e42-8962-11e7-b8af-70106fb01274", "b669a286-8962-11e7-b8af-70106fb01274", "b663212d-8962-11e7-b8af-70106fb01274", "b646c9e8-8962-11e7-b8af-70106fb01274", "b646245d-8962-11e7-b8af-70106fb01274", "b66465f4-8962-11e7-b8af-70106fb01274", "b6460b1d-8962-11e7-b8af-70106fb01274", "b64559c0-8962-11e7-b8af-70106fb01274", "b65d7048-8962-11e7-b8af-70106fb01274", "b64f6db6-8962-11e7-b8af-70106fb01274", "b6590984-8962-11e7-b8af-70106fb01274", "b6517036-8962-11e7-b8af-70106fb01274", "b6478cd5-8962-11e7-b8af-70106fb01274", "b66af416-8962-11e7-b8af-70106fb01274", "b6459374-8962-11e7-b8af-70106fb01274", "b6594fab-8962-11e7-b8af-70106fb01274", "b65a8636-8962-11e7-b8af-70106fb01274", "b65e1b9a-8962-11e7-b8af-70106fb01274", "b6472942-8962-11e7-b8af-70106fb01274", "b66c9945-8962-11e7-b8af-70106fb01274", "b661198c-8962-11e7-b8af-70106fb01274", "b667cacd-8962-11e7-b8af-70106fb01274", "b653451f-8962-11e7-b8af-70106fb01274", "b66de58a-8962-11e7-b8af-70106fb01274", "b66a2d52-8962-11e7-b8af-70106fb01274", "b663941d-8962-11e7-b8af-70106fb01274", "b64711e1-8962-11e7-b8af-70106fb01274", "b663f373-8962-11e7-b8af-70106fb01274", "b66e3975-8962-11e7-b8af-70106fb01274", "b6671942-8962-11e7-b8af-70106fb01274", "b66d0bd7-8962-11e7-b8af-70106fb01274", "b6577028-8962-11e7-b8af-70106fb01274", "b669d211-8962-11e7-b8af-70106fb01274", "b663876b-8962-11e7-b8af-70106fb01274", "b66d185a-8962-11e7-b8af-70106fb01274", "b66968a9-8962-11e7-b8af-70106fb01274", "b662b2f0-8962-11e7-b8af-70106fb01274", "b65b7f2f-8962-11e7-b8af-70106fb01274", "b657d8ac-8962-11e7-b8af-70106fb01274", "b6459b97-8962-11e7-b8af-70106fb01274", "b64666c6-8962-11e7-b8af-70106fb01274", "b6462389-8962-11e7-b8af-70106fb01274", "b6454ae2-8962-11e7-b8af-70106fb01274", "b6501977-8962-11e7-b8af-70106fb01274", "b66c6b7c-8962-11e7-b8af-70106fb01274", "b6626dcd-8962-11e7-b8af-70106fb01274", "b65936cf-8962-11e7-b8af-70106fb01274", "b6479d31-8962-11e7-b8af-70106fb01274", "b661cffe-8962-11e7-b8af-70106fb01274", "b6689308-8962-11e7-b8af-70106fb01274", "b6677440-8962-11e7-b8af-70106fb01274", "b65758b4-8962-11e7-b8af-70106fb01274", "b647754e-8962-11e7-b8af-70106fb01274", "b64e18aa-8962-11e7-b8af-70106fb01274", "b653d5cd-8962-11e7-b8af-70106fb01274", "b6592c7f-8962-11e7-b8af-70106fb01274", "b64def8c-8962-11e7-b8af-70106fb01274", "b6461ee0-8962-11e7-b8af-70106fb01274", "b65e6789-8962-11e7-b8af-70106fb01274", "b646c6c2-8962-11e7-b8af-70106fb01274", "b64536bb-8962-11e7-b8af-70106fb01274", "b6609b51-8962-11e7-b8af-70106fb01274", "b66b46b7-8962-11e7-b8af-70106fb01274", "b664f785-8962-11e7-b8af-70106fb01274", "b64522be-8962-11e7-b8af-70106fb01274", "b64dd1ee-8962-11e7-b8af-70106fb01274", "b660d70e-8962-11e7-b8af-70106fb01274", "b65fc64f-8962-11e7-b8af-70106fb01274", "b656a8ec-8962-11e7-b8af-70106fb01274", "b6478ab7-8962-11e7-b8af-70106fb01274", "b646c93c-8962-11e7-b8af-70106fb01274", "b65d9156-8962-11e7-b8af-70106fb01274", "b656f683-8962-11e7-b8af-70106fb01274", "b6567c3f-8962-11e7-b8af-70106fb01274", "b66d2451-8962-11e7-b8af-70106fb01274", "b666a6b9-8962-11e7-b8af-70106fb01274", "b65e5391-8962-11e7-b8af-70106fb01274", "b65977ee-8962-11e7-b8af-70106fb01274", "b66805aa-8962-11e7-b8af-70106fb01274", "b647acbe-8962-11e7-b8af-70106fb01274", "b6460aee-8962-11e7-b8af-70106fb01274", "b64763bb-8962-11e7-b8af-70106fb01274", "b645d1c2-8962-11e7-b8af-70106fb01274", "b645b7cb-8962-11e7-b8af-70106fb01274", "b667ec87-8962-11e7-b8af-70106fb01274", "b64e0933-8962-11e7-b8af-70106fb01274", "b6461793-8962-11e7-b8af-70106fb01274", "b664d476-8962-11e7-b8af-70106fb01274", "b6662fe9-8962-11e7-b8af-70106fb01274", "a7ed0337-c607-4c90-b74a-daf4389c556b", "b6653470-8962-11e7-b8af-70106fb01274", "b65e93d8-8962-11e7-b8af-70106fb01274", "b65b02f2-8962-11e7-b8af-70106fb01274", "b65651de-8962-11e7-b8af-70106fb01274", "b65824a4-8962-11e7-b8af-70106fb01274", "b646de67-8962-11e7-b8af-70106fb01274", "b662f3d4-8962-11e7-b8af-70106fb01274", "b66e64ca-8962-11e7-b8af-70106fb01274", "b65ceaff-8962-11e7-b8af-70106fb01274", "b66504b9-8962-11e7-b8af-70106fb01274", "b6671da0-8962-11e7-b8af-70106fb01274", "b65d0a3b-8962-11e7-b8af-70106fb01274", "b65758fa-8962-11e7-b8af-70106fb01274", "b65b4d19-8962-11e7-b8af-70106fb01274", "b65b08e4-8962-11e7-b8af-70106fb01274", "b6635c93-8962-11e7-b8af-70106fb01274", "b65f12cb-8962-11e7-b8af-70106fb01274", "b65d3186-8962-11e7-b8af-70106fb01274", "b650a5ee-8962-11e7-b8af-70106fb01274", "b669cd21-8962-11e7-b8af-70106fb01274", "b6644676-8962-11e7-b8af-70106fb01274", "b64e6ea5-8962-11e7-b8af-70106fb01274", "b645c321-8962-11e7-b8af-70106fb01274", "b65cbf9b-8962-11e7-b8af-70106fb01274", "b65cbfe2-8962-11e7-b8af-70106fb01274", "b650683c-8962-11e7-b8af-70106fb01274", "b65b5033-8962-11e7-b8af-70106fb01274", "b6465c8a-8962-11e7-b8af-70106fb01274", "b65b01b3-8962-11e7-b8af-70106fb01274", "b66737c7-8962-11e7-b8af-70106fb01274", "b6639769-8962-11e7-b8af-70106fb01274", "b6472b3e-8962-11e7-b8af-70106fb01274", "b66b1fc1-8962-11e7-b8af-70106fb01274", "b66357d4-8962-11e7-b8af-70106fb01274", "b658ba43-8962-11e7-b8af-70106fb01274", "b6666fca-8962-11e7-b8af-70106fb01274", "b662b741-8962-11e7-b8af-70106fb01274", "b64673c4-8962-11e7-b8af-70106fb01274", "b6599062-8962-11e7-b8af-70106fb01274", "b6667d20-8962-11e7-b8af-70106fb01274", "b65305a1-8962-11e7-b8af-70106fb01274", "b645772d-8962-11e7-b8af-70106fb01274", "b66a28f3-8962-11e7-b8af-70106fb01274", "b65678b0-8962-11e7-b8af-70106fb01274", "b662b1d0-8962-11e7-b8af-70106fb01274", "b6609dc3-8962-11e7-b8af-70106fb01274", "b6459b1c-8962-11e7-b8af-70106fb01274", "b65d7b32-8962-11e7-b8af-70106fb01274", "b6597fec-8962-11e7-b8af-70106fb01274", "b665ab09-8962-11e7-b8af-70106fb01274", "b6470ba8-8962-11e7-b8af-70106fb01274", "b666a8c8-8962-11e7-b8af-70106fb01274", "b65dd51b-8962-11e7-b8af-70106fb01274", "a7f202ca-f45d-43e3-8918-386c42aaeaf8", "b65dced2-8962-11e7-b8af-70106fb01274", "b65bf12c-8962-11e7-b8af-70106fb01274", "b658860d-8962-11e7-b8af-70106fb01274", "b64ef8d6-8962-11e7-b8af-70106fb01274", "b646b092-8962-11e7-b8af-70106fb01274", "b667f5e2-8962-11e7-b8af-70106fb01274", "b6611230-8962-11e7-b8af-70106fb01274", "b663231c-8962-11e7-b8af-70106fb01274", "b65dfa59-8962-11e7-b8af-70106fb01274", "b650a8ab-8962-11e7-b8af-70106fb01274", "b66b9f79-8962-11e7-b8af-70106fb01274", "b669fff5-8962-11e7-b8af-70106fb01274", "b656836f-8962-11e7-b8af-70106fb01274", "b666e619-8962-11e7-b8af-70106fb01274", "b65bc55c-8962-11e7-b8af-70106fb01274", "b66e3794-8962-11e7-b8af-70106fb01274", "b668e652-8962-11e7-b8af-70106fb01274", "b65ec600-8962-11e7-b8af-70106fb01274", "a7ed036a-9f8f-44d8-8d2d-04e656d8acae", "b6591cd4-8962-11e7-b8af-70106fb01274", "b64fb339-8962-11e7-b8af-70106fb01274", "b66ca21b-8962-11e7-b8af-70106fb01274", "b65fe0ca-8962-11e7-b8af-70106fb01274", "b6458f1b-8962-11e7-b8af-70106fb01274", "b65748d8-8962-11e7-b8af-70106fb01274", "b6519152-8962-11e7-b8af-70106fb01274", "b66a35de-8962-11e7-b8af-70106fb01274", "b65882a5-8962-11e7-b8af-70106fb01274", "b66c3fcc-8962-11e7-b8af-70106fb01274", "b6453f7f-8962-11e7-b8af-70106fb01274", "b66cf724-8962-11e7-b8af-70106fb01274", "b66b0d95-8962-11e7-b8af-70106fb01274", "b6637532-8962-11e7-b8af-70106fb01274", "b65c39ba-8962-11e7-b8af-70106fb01274", "b645974d-8962-11e7-b8af-70106fb01274", "b6629bf7-8962-11e7-b8af-70106fb01274", "b6671aff-8962-11e7-b8af-70106fb01274", "b65f34dc-8962-11e7-b8af-70106fb01274", "a7ec0375-0ac8-41c8-b51c-9787993793e9", "b66caeb3-8962-11e7-b8af-70106fb01274", "b6629af4-8962-11e7-b8af-70106fb01274", "b64fc6bf-8962-11e7-b8af-70106fb01274", "b66d5152-8962-11e7-b8af-70106fb01274", "b66a23d2-8962-11e7-b8af-70106fb01274", "b6651651-8962-11e7-b8af-70106fb01274", "b663c017-8962-11e7-b8af-70106fb01274", "a7ed034e-7ac5-40de-af46-cb7fa7fcab6c", "a7ed03ad-2415-409a-923c-b40e4a3ac921", "b669ae04-8962-11e7-b8af-70106fb01274", "b664bbf9-8962-11e7-b8af-70106fb01274", "b657ac13-8962-11e7-b8af-70106fb01274", "b65c55ef-8962-11e7-b8af-70106fb01274", "b6591b02-8962-11e7-b8af-70106fb01274", "b6588196-8962-11e7-b8af-70106fb01274", "b6515a3c-8962-11e7-b8af-70106fb01274", "b662df7c-8962-11e7-b8af-70106fb01274", "b65de5f8-8962-11e7-b8af-70106fb01274", "b652c7ca-8962-11e7-b8af-70106fb01274", "b645344c-8962-11e7-b8af-70106fb01274", "b664cefb-8962-11e7-b8af-70106fb01274", "b6648500-8962-11e7-b8af-70106fb01274", "b65dcb42-8962-11e7-b8af-70106fb01274", "b6505704-8962-11e7-b8af-70106fb01274", "a7ed0416-4cb7-43e1-a7df-02410a8c8d02", "b6673022-8962-11e7-b8af-70106fb01274", "b64f7e7d-8962-11e7-b8af-70106fb01274", "b660d285-8962-11e7-b8af-70106fb01274", "b662e713-8962-11e7-b8af-70106fb01274", "b66203b7-8962-11e7-b8af-70106fb01274", "b646a70c-8962-11e7-b8af-70106fb01274", "b645cf8d-8962-11e7-b8af-70106fb01274", "b645acc7-8962-11e7-b8af-70106fb01274", "b666c426-8962-11e7-b8af-70106fb01274", "b6517977-8962-11e7-b8af-70106fb01274", "a7ed0411-a283-469d-95c2-24c15e07ac99", "b666aba1-8962-11e7-b8af-70106fb01274", "b66ce977-8962-11e7-b8af-70106fb01274", "b6605546-8962-11e7-b8af-70106fb01274", "b65a9cfa-8962-11e7-b8af-70106fb01274", "b65e1c29-8962-11e7-b8af-70106fb01274", "b64588d7-8962-11e7-b8af-70106fb01274", "b660f8a6-8962-11e7-b8af-70106fb01274", "b646236f-8962-11e7-b8af-70106fb01274", "b645f228-8962-11e7-b8af-70106fb01274", "a7ed0421-7e68-448c-9ae6-54e2ea4aa008", "b66b14ab-8962-11e7-b8af-70106fb01274", "a7ec0000-1a92-4871-b488-1d07fa8922be", "b646a9fe-8962-11e7-b8af-70106fb01274", "b66e697d-8962-11e7-b8af-70106fb01274", "b65f18c9-8962-11e7-b8af-70106fb01274", "b6671580-8962-11e7-b8af-70106fb01274", "b65fd08a-8962-11e7-b8af-70106fb01274", "b6595659-8962-11e7-b8af-70106fb01274", "b65bd4a5-8962-11e7-b8af-70106fb01274", "b64f17c1-8962-11e7-b8af-70106fb01274", "b662dbc0-8962-11e7-b8af-70106fb01274", "b645cfb7-8962-11e7-b8af-70106fb01274", "b6605a87-8962-11e7-b8af-70106fb01274", "b6659d35-8962-11e7-b8af-70106fb01274", "b6590a70-8962-11e7-b8af-70106fb01274", "b66a46fe-8962-11e7-b8af-70106fb01274", "b6678278-8962-11e7-b8af-70106fb01274", "b6638a48-8962-11e7-b8af-70106fb01274", "b66349e7-8962-11e7-b8af-70106fb01274", "b662f033-8962-11e7-b8af-70106fb01274", "b650d1a7-8962-11e7-b8af-70106fb01274", "b64ed25e-8962-11e7-b8af-70106fb01274", "b645a767-8962-11e7-b8af-70106fb01274", "b65ed98c-8962-11e7-b8af-70106fb01274", "b65b9165-8962-11e7-b8af-70106fb01274", "b65b1433-8962-11e7-b8af-70106fb01274", "a7ed041d-70d2-45a8-b4e0-857b48d448e2", "b66cd91e-8962-11e7-b8af-70106fb01274", "b664e016-8962-11e7-b8af-70106fb01274", "b65bc285-8962-11e7-b8af-70106fb01274", "b6587ac4-8962-11e7-b8af-70106fb01274", "b65cc451-8962-11e7-b8af-70106fb01274", "b65ca178-8962-11e7-b8af-70106fb01274", "b6505791-8962-11e7-b8af-70106fb01274", "b6693851-8962-11e7-b8af-70106fb01274", "b65b4e23-8962-11e7-b8af-70106fb01274", "b66b548b-8962-11e7-b8af-70106fb01274", "b6651435-8962-11e7-b8af-70106fb01274", "b6607a0e-8962-11e7-b8af-70106fb01274", "b65a44fc-8962-11e7-b8af-70106fb01274", "b66b8bdb-8962-11e7-b8af-70106fb01274", "b6662760-8962-11e7-b8af-70106fb01274", "b65ea54a-8962-11e7-b8af-70106fb01274", "b65dab2d-8962-11e7-b8af-70106fb01274", "b6511137-8962-11e7-b8af-70106fb01274", "b66a450a-8962-11e7-b8af-70106fb01274", "b66c2901-8962-11e7-b8af-70106fb01274", "b6506c54-8962-11e7-b8af-70106fb01274", "b6453903-8962-11e7-b8af-70106fb01274", "b66320b7-8962-11e7-b8af-70106fb01274", "b659546c-8962-11e7-b8af-70106fb01274", "b645d6b4-8962-11e7-b8af-70106fb01274", "b66a86a3-8962-11e7-b8af-70106fb01274", "b66524d7-8962-11e7-b8af-70106fb01274", "b66468c4-8962-11e7-b8af-70106fb01274", "b663f047-8962-11e7-b8af-70106fb01274", "b6652a08-8962-11e7-b8af-70106fb01274", "b65a7bc2-8962-11e7-b8af-70106fb01274", "b66bfdeb-8962-11e7-b8af-70106fb01274", "b65fa2a5-8962-11e7-b8af-70106fb01274", "b65da2fa-8962-11e7-b8af-70106fb01274", "b66cf79c-8962-11e7-b8af-70106fb01274", "b6453742-8962-11e7-b8af-70106fb01274", "b6639664-8962-11e7-b8af-70106fb01274", "b668fca4-8962-11e7-b8af-70106fb01274", "b66838e7-8962-11e7-b8af-70106fb01274", "b66bb082-8962-11e7-b8af-70106fb01274", "b66865c9-8962-11e7-b8af-70106fb01274", "b6503177-8962-11e7-b8af-70106fb01274", "b662d355-8962-11e7-b8af-70106fb01274", "b65ffadd-8962-11e7-b8af-70106fb01274", "b65884c1-8962-11e7-b8af-70106fb01274", "b656b8ec-8962-11e7-b8af-70106fb01274", "b665cab7-8962-11e7-b8af-70106fb01274", "b65e1976-8962-11e7-b8af-70106fb01274", "b65fa017-8962-11e7-b8af-70106fb01274", "b65c4716-8962-11e7-b8af-70106fb01274", "b6587be7-8962-11e7-b8af-70106fb01274", "b657b549-8962-11e7-b8af-70106fb01274", "b6476096-8962-11e7-b8af-70106fb01274", "b6456cfb-8962-11e7-b8af-70106fb01274", "b64546f1-8962-11e7-b8af-70106fb01274", "b65799cc-8962-11e7-b8af-70106fb01274", "b645d2b1-8962-11e7-b8af-70106fb01274", "b664bf05-8962-11e7-b8af-70106fb01274", "b6631641-8962-11e7-b8af-70106fb01274", "b659e159-8962-11e7-b8af-70106fb01274", "b645b916-8962-11e7-b8af-70106fb01274", "b64fa24f-8962-11e7-b8af-70106fb01274", "b659212f-8962-11e7-b8af-70106fb01274", "b65fa95a-8962-11e7-b8af-70106fb01274", "b65bccef-8962-11e7-b8af-70106fb01274", "b65f4dc6-8962-11e7-b8af-70106fb01274", "b658890a-8962-11e7-b8af-70106fb01274", "b666c087-8962-11e7-b8af-70106fb01274", "b6456ed8-8962-11e7-b8af-70106fb01274", "b65f56f2-8962-11e7-b8af-70106fb01274", "b65a1cb5-8962-11e7-b8af-70106fb01274", "b6531429-8962-11e7-b8af-70106fb01274", "b645ace0-8962-11e7-b8af-70106fb01274", "b645589b-8962-11e7-b8af-70106fb01274", "b6653def-8962-11e7-b8af-70106fb01274", "b65b7fb9-8962-11e7-b8af-70106fb01274", "b6453359-8962-11e7-b8af-70106fb01274", "b663ad43-8962-11e7-b8af-70106fb01274", "b659e52e-8962-11e7-b8af-70106fb01274", "b6591a15-8962-11e7-b8af-70106fb01274", "b666fe36-8962-11e7-b8af-70106fb01274", "b65c5fd7-8962-11e7-b8af-70106fb01274", "b6590f57-8962-11e7-b8af-70106fb01274", "b6477745-8962-11e7-b8af-70106fb01274", "b6460242-8962-11e7-b8af-70106fb01274", "b66705ba-8962-11e7-b8af-70106fb01274", "b6601564-8962-11e7-b8af-70106fb01274", "b645dcbb-8962-11e7-b8af-70106fb01274", "b66b3706-8962-11e7-b8af-70106fb01274", "b661edf6-8962-11e7-b8af-70106fb01274", "b665cbe0-8962-11e7-b8af-70106fb01274", "b6656eeb-8962-11e7-b8af-70106fb01274", "b651576d-8962-11e7-b8af-70106fb01274", "b65cb550-8962-11e7-b8af-70106fb01274", "b65f7d26-8962-11e7-b8af-70106fb01274", "b64672bc-8962-11e7-b8af-70106fb01274", "b6461c7f-8962-11e7-b8af-70106fb01274", "b6459b84-8962-11e7-b8af-70106fb01274", "b663f557-8962-11e7-b8af-70106fb01274", "b6623748-8962-11e7-b8af-70106fb01274", "b65ba713-8962-11e7-b8af-70106fb01274", "b6479c09-8962-11e7-b8af-70106fb01274", "b65f3e7e-8962-11e7-b8af-70106fb01274", "b66167fe-8962-11e7-b8af-70106fb01274", "b65f59f5-8962-11e7-b8af-70106fb01274", "b658d72a-8962-11e7-b8af-70106fb01274", "b6657374-8962-11e7-b8af-70106fb01274", "b657e540-8962-11e7-b8af-70106fb01274", "b6567e14-8962-11e7-b8af-70106fb01274", "b64faa8e-8962-11e7-b8af-70106fb01274", "b6453138-8962-11e7-b8af-70106fb01274", "b663f3c6-8962-11e7-b8af-70106fb01274", "b645aec0-8962-11e7-b8af-70106fb01274", "b665cda5-8962-11e7-b8af-70106fb01274", "b65a2f61-8962-11e7-b8af-70106fb01274", "b6570c1e-8962-11e7-b8af-70106fb01274", "b6466718-8962-11e7-b8af-70106fb01274", "b65f3d5f-8962-11e7-b8af-70106fb01274", "b6571761-8962-11e7-b8af-70106fb01274", "b64775d3-8962-11e7-b8af-70106fb01274", "b6459e43-8962-11e7-b8af-70106fb01274", "b64f5841-8962-11e7-b8af-70106fb01274", "b64ed1b8-8962-11e7-b8af-70106fb01274", "b66cf7d4-8962-11e7-b8af-70106fb01274", "b65f5a39-8962-11e7-b8af-70106fb01274", "b659a76c-8962-11e7-b8af-70106fb01274", "b65978c2-8962-11e7-b8af-70106fb01274", "b6582f54-8962-11e7-b8af-70106fb01274", "a7ee02ed-1e8b-4671-a69b-40c049458c9f", "b6652264-8962-11e7-b8af-70106fb01274", "b65a27b9-8962-11e7-b8af-70106fb01274", "b665e7ed-8962-11e7-b8af-70106fb01274", "b6468da4-8962-11e7-b8af-70106fb01274", "b66a1bb6-8962-11e7-b8af-70106fb01274", "b65f9f7b-8962-11e7-b8af-70106fb01274", "b659c474-8962-11e7-b8af-70106fb01274", "b6572412-8962-11e7-b8af-70106fb01274", "b647a7db-8962-11e7-b8af-70106fb01274", "b6612e11-8962-11e7-b8af-70106fb01274", "b66037e1-8962-11e7-b8af-70106fb01274", "b6524566-8962-11e7-b8af-70106fb01274", "b645a124-8962-11e7-b8af-70106fb01274", "b65b0001-8962-11e7-b8af-70106fb01274", "b66bb6ae-8962-11e7-b8af-70106fb01274", "b663f325-8962-11e7-b8af-70106fb01274", "b65bf569-8962-11e7-b8af-70106fb01274", "b66b4536-8962-11e7-b8af-70106fb01274", "b65ec646-8962-11e7-b8af-70106fb01274", "b66bb854-8962-11e7-b8af-70106fb01274", "b665046d-8962-11e7-b8af-70106fb01274", "b6610cf6-8962-11e7-b8af-70106fb01274", "b65957c1-8962-11e7-b8af-70106fb01274", "a7ee02ec-6232-4271-b23a-ab1c031521a2", "b66a1a1c-8962-11e7-b8af-70106fb01274", "b6618f54-8962-11e7-b8af-70106fb01274", "b65da917-8962-11e7-b8af-70106fb01274", "b652d06f-8962-11e7-b8af-70106fb01274", "b66a9601-8962-11e7-b8af-70106fb01274", "b658c848-8962-11e7-b8af-70106fb01274", "b6462b27-8962-11e7-b8af-70106fb01274", "b659c545-8962-11e7-b8af-70106fb01274", "b6574158-8962-11e7-b8af-70106fb01274", "b658873f-8962-11e7-b8af-70106fb01274", "b65646ab-8962-11e7-b8af-70106fb01274", "b64d8dd5-8962-11e7-b8af-70106fb01274", "b66a306e-8962-11e7-b8af-70106fb01274", "b661c01e-8962-11e7-b8af-70106fb01274", "b6592b96-8962-11e7-b8af-70106fb01274", "b64ed77c-8962-11e7-b8af-70106fb01274", "b645d34c-8962-11e7-b8af-70106fb01274", "b66b4853-8962-11e7-b8af-70106fb01274", "b6613940-8962-11e7-b8af-70106fb01274", "b65d675d-8962-11e7-b8af-70106fb01274", "b65c9cbf-8962-11e7-b8af-70106fb01274", "b65b8eb6-8962-11e7-b8af-70106fb01274", "b6571f33-8962-11e7-b8af-70106fb01274", "b645a275-8962-11e7-b8af-70106fb01274", "b645833c-8962-11e7-b8af-70106fb01274", "b6667573-8962-11e7-b8af-70106fb01274", "b6620690-8962-11e7-b8af-70106fb01274", "b65f55b7-8962-11e7-b8af-70106fb01274", "b645b9bd-8962-11e7-b8af-70106fb01274", "b6632604-8962-11e7-b8af-70106fb01274", "b64f62bc-8962-11e7-b8af-70106fb01274", "b64e6d35-8962-11e7-b8af-70106fb01274", "b6681b32-8962-11e7-b8af-70106fb01274", "b6525228-8962-11e7-b8af-70106fb01274", "b6453439-8962-11e7-b8af-70106fb01274", "b64526b7-8962-11e7-b8af-70106fb01274", "b665c333-8962-11e7-b8af-70106fb01274", "b65a7b8c-8962-11e7-b8af-70106fb01274", "b6564051-8962-11e7-b8af-70106fb01274", "b64656a1-8962-11e7-b8af-70106fb01274", "b64f22fe-8962-11e7-b8af-70106fb01274", "b66a54c3-8962-11e7-b8af-70106fb01274", "b668e31c-8962-11e7-b8af-70106fb01274", "b6664bcb-8962-11e7-b8af-70106fb01274", "b65e19eb-8962-11e7-b8af-70106fb01274", "b64e79b1-8962-11e7-b8af-70106fb01274", "b6463dc3-8962-11e7-b8af-70106fb01274", "b645f441-8962-11e7-b8af-70106fb01274", "b65e736f-8962-11e7-b8af-70106fb01274", "b65bdae3-8962-11e7-b8af-70106fb01274", "b6652c04-8962-11e7-b8af-70106fb01274", "b6645516-8962-11e7-b8af-70106fb01274", "b662d063-8962-11e7-b8af-70106fb01274", "b6620d93-8962-11e7-b8af-70106fb01274", "b6604556-8962-11e7-b8af-70106fb01274", "b65b8f7e-8962-11e7-b8af-70106fb01274", "b659bb50-8962-11e7-b8af-70106fb01274", "b66e6645-8962-11e7-b8af-70106fb01274", "b66bb77e-8962-11e7-b8af-70106fb01274", "b66a95c3-8962-11e7-b8af-70106fb01274", "b65b8df4-8962-11e7-b8af-70106fb01274", "b6479474-8962-11e7-b8af-70106fb01274", "b645fc60-8962-11e7-b8af-70106fb01274", "b645d21d-8962-11e7-b8af-70106fb01274", "b64552dc-8962-11e7-b8af-70106fb01274", "b6691a7e-8962-11e7-b8af-70106fb01274", "b6659d8a-8962-11e7-b8af-70106fb01274", "b6461f8f-8962-11e7-b8af-70106fb01274", "b65a6e6e-8962-11e7-b8af-70106fb01274", "b66d9782-8962-11e7-b8af-70106fb01274", "b6687f65-8962-11e7-b8af-70106fb01274", "b65f9931-8962-11e7-b8af-70106fb01274", "b65b6162-8962-11e7-b8af-70106fb01274", "b6503e61-8962-11e7-b8af-70106fb01274", "b6458df5-8962-11e7-b8af-70106fb01274", "b65b7116-8962-11e7-b8af-70106fb01274", "b6459118-8962-11e7-b8af-70106fb01274", "b6639119-8962-11e7-b8af-70106fb01274", "b657ae4d-8962-11e7-b8af-70106fb01274", "b64ddbff-8962-11e7-b8af-70106fb01274", "b6643580-8962-11e7-b8af-70106fb01274", "b665030f-8962-11e7-b8af-70106fb01274", "b6621090-8962-11e7-b8af-70106fb01274", "b65e2d30-8962-11e7-b8af-70106fb01274", "b665f759-8962-11e7-b8af-70106fb01274", "b65ea498-8962-11e7-b8af-70106fb01274", "b6577daa-8962-11e7-b8af-70106fb01274", "b6573966-8962-11e7-b8af-70106fb01274", "b6529eb4-8962-11e7-b8af-70106fb01274", "b6625bca-8962-11e7-b8af-70106fb01274", "b65e70da-8962-11e7-b8af-70106fb01274", "b65e4f51-8962-11e7-b8af-70106fb01274", "b65e4ba0-8962-11e7-b8af-70106fb01274", "b656a91e-8962-11e7-b8af-70106fb01274", "b6466b5d-8962-11e7-b8af-70106fb01274", "b66cf320-8962-11e7-b8af-70106fb01274", "b669b8ed-8962-11e7-b8af-70106fb01274", "b666d60d-8962-11e7-b8af-70106fb01274", "b665cb02-8962-11e7-b8af-70106fb01274", "b6652600-8962-11e7-b8af-70106fb01274", "b664ad64-8962-11e7-b8af-70106fb01274", "b65fb911-8962-11e7-b8af-70106fb01274", "b6561afb-8962-11e7-b8af-70106fb01274", "b66e7230-8962-11e7-b8af-70106fb01274", "b66518a6-8962-11e7-b8af-70106fb01274", "b6639bed-8962-11e7-b8af-70106fb01274", "b65e310f-8962-11e7-b8af-70106fb01274", "b64f0d6d-8962-11e7-b8af-70106fb01274", "b64540db-8962-11e7-b8af-70106fb01274", "b66e57c3-8962-11e7-b8af-70106fb01274", "b66bc54a-8962-11e7-b8af-70106fb01274", "b65e3591-8962-11e7-b8af-70106fb01274", "b65945eb-8962-11e7-b8af-70106fb01274", "b6517d9a-8962-11e7-b8af-70106fb01274", "b6476217-8962-11e7-b8af-70106fb01274", "b6459a3d-8962-11e7-b8af-70106fb01274", "b6650102-8962-11e7-b8af-70106fb01274", "b66358e3-8962-11e7-b8af-70106fb01274", "b65e54e0-8962-11e7-b8af-70106fb01274", "b65735d6-8962-11e7-b8af-70106fb01274", "b66b4f3c-8962-11e7-b8af-70106fb01274", "b666e161-8962-11e7-b8af-70106fb01274", "b661cf28-8962-11e7-b8af-70106fb01274", "b65dbc62-8962-11e7-b8af-70106fb01274", "b65d2213-8962-11e7-b8af-70106fb01274", "b659e5be-8962-11e7-b8af-70106fb01274", "b6573921-8962-11e7-b8af-70106fb01274", "b64f903a-8962-11e7-b8af-70106fb01274", "b66cfae1-8962-11e7-b8af-70106fb01274", "b66ba359-8962-11e7-b8af-70106fb01274", "b667905b-8962-11e7-b8af-70106fb01274", "b6652330-8962-11e7-b8af-70106fb01274", "b664729e-8962-11e7-b8af-70106fb01274", "b66230ce-8962-11e7-b8af-70106fb01274", "b65c0aec-8962-11e7-b8af-70106fb01274", "b65a040d-8962-11e7-b8af-70106fb01274", "b6478435-8962-11e7-b8af-70106fb01274", "b6458ade-8962-11e7-b8af-70106fb01274", "b6457e67-8962-11e7-b8af-70106fb01274", "b6455a62-8962-11e7-b8af-70106fb01274", "b66cf3c7-8962-11e7-b8af-70106fb01274", "b6662049-8962-11e7-b8af-70106fb01274", "b6652375-8962-11e7-b8af-70106fb01274", "b65b6a73-8962-11e7-b8af-70106fb01274", "b64f9705-8962-11e7-b8af-70106fb01274", "b646b23d-8962-11e7-b8af-70106fb01274", "b645751f-8962-11e7-b8af-70106fb01274", "b6696585-8962-11e7-b8af-70106fb01274", "b65ca0a9-8962-11e7-b8af-70106fb01274", "b64556ca-8962-11e7-b8af-70106fb01274", "b66b1b82-8962-11e7-b8af-70106fb01274", "b6452bc3-8962-11e7-b8af-70106fb01274", "b668bab8-8962-11e7-b8af-70106fb01274", "b6642db1-8962-11e7-b8af-70106fb01274", "b6608d3b-8962-11e7-b8af-70106fb01274", "b65a14e4-8962-11e7-b8af-70106fb01274", "b64725b4-8962-11e7-b8af-70106fb01274", "b645979d-8962-11e7-b8af-70106fb01274", "b6453834-8962-11e7-b8af-70106fb01274", "b66b2ae3-8962-11e7-b8af-70106fb01274", "b6660209-8962-11e7-b8af-70106fb01274", "b6646743-8962-11e7-b8af-70106fb01274", "b658d992-8962-11e7-b8af-70106fb01274", "b6588aa5-8962-11e7-b8af-70106fb01274", "b65885a2-8962-11e7-b8af-70106fb01274", "b64e7a52-8962-11e7-b8af-70106fb01274", "a7e80207-aa02-45ee-b154-34ac3d8d76bc", "b66bf3e4-8962-11e7-b8af-70106fb01274", "b664b98f-8962-11e7-b8af-70106fb01274", "b659e3da-8962-11e7-b8af-70106fb01274", "b6466f3d-8962-11e7-b8af-70106fb01274", "b65dca8f-8962-11e7-b8af-70106fb01274", "b6454c51-8962-11e7-b8af-70106fb01274", "b66a2203-8962-11e7-b8af-70106fb01274", "b6676cd6-8962-11e7-b8af-70106fb01274", "b6625b8b-8962-11e7-b8af-70106fb01274", "b65f3f62-8962-11e7-b8af-70106fb01274", "b65eddbf-8962-11e7-b8af-70106fb01274", "b65c05f1-8962-11e7-b8af-70106fb01274", "b65325f0-8962-11e7-b8af-70106fb01274", "b66b7962-8962-11e7-b8af-70106fb01274", "b667c07a-8962-11e7-b8af-70106fb01274", "b6653db2-8962-11e7-b8af-70106fb01274", "b65abce3-8962-11e7-b8af-70106fb01274", "b659dcb7-8962-11e7-b8af-70106fb01274", "b66e40d9-8962-11e7-b8af-70106fb01274", "b66b99e6-8962-11e7-b8af-70106fb01274", "b668189b-8962-11e7-b8af-70106fb01274", "b65e9b71-8962-11e7-b8af-70106fb01274", "b65dda36-8962-11e7-b8af-70106fb01274", "b65b8980-8962-11e7-b8af-70106fb01274", "b653b38e-8962-11e7-b8af-70106fb01274", "b64dc372-8962-11e7-b8af-70106fb01274", "b646270e-8962-11e7-b8af-70106fb01274", "b645a9c2-8962-11e7-b8af-70106fb01274", "b66bb6f3-8962-11e7-b8af-70106fb01274", "b66b49fe-8962-11e7-b8af-70106fb01274", "b66a8f45-8962-11e7-b8af-70106fb01274", "b6643120-8962-11e7-b8af-70106fb01274", "b650ef4d-8962-11e7-b8af-70106fb01274", "b64e797f-8962-11e7-b8af-70106fb01274", "b64583b8-8962-11e7-b8af-70106fb01274", "b66bf6ba-8962-11e7-b8af-70106fb01274", "b6678a1a-8962-11e7-b8af-70106fb01274", "b666d731-8962-11e7-b8af-70106fb01274", "b65e562b-8962-11e7-b8af-70106fb01274", "b65cf3cc-8962-11e7-b8af-70106fb01274", "b65a6ea5-8962-11e7-b8af-70106fb01274", "b65a5dc4-8962-11e7-b8af-70106fb01274", "b65834d4-8962-11e7-b8af-70106fb01274", "b6511005-8962-11e7-b8af-70106fb01274", "b650118f-8962-11e7-b8af-70106fb01274", "b6455d14-8962-11e7-b8af-70106fb01274", "b66bb89d-8962-11e7-b8af-70106fb01274", "b669b4cc-8962-11e7-b8af-70106fb01274", "b669ad3b-8962-11e7-b8af-70106fb01274", "b66535f1-8962-11e7-b8af-70106fb01274", "b6603f3a-8962-11e7-b8af-70106fb01274", "b65ef462-8962-11e7-b8af-70106fb01274", "b659e725-8962-11e7-b8af-70106fb01274", "b651a551-8962-11e7-b8af-70106fb01274", "b6472e97-8962-11e7-b8af-70106fb01274", "b66c4be4-8962-11e7-b8af-70106fb01274", "b6694546-8962-11e7-b8af-70106fb01274", "b661bfeb-8962-11e7-b8af-70106fb01274", "b65ed722-8962-11e7-b8af-70106fb01274", "b6503a33-8962-11e7-b8af-70106fb01274", "b646b3f5-8962-11e7-b8af-70106fb01274", "b646b399-8962-11e7-b8af-70106fb01274", "b64679fe-8962-11e7-b8af-70106fb01274", "b6465662-8962-11e7-b8af-70106fb01274", "b652fda4-8962-11e7-b8af-70106fb01274", "b6529efb-8962-11e7-b8af-70106fb01274", "b6478553-8962-11e7-b8af-70106fb01274", "b66c3234-8962-11e7-b8af-70106fb01274", "b65c265d-8962-11e7-b8af-70106fb01274", "b65b774c-8962-11e7-b8af-70106fb01274", "b659efda-8962-11e7-b8af-70106fb01274", "b6456bd3-8962-11e7-b8af-70106fb01274", "b661d84f-8962-11e7-b8af-70106fb01274", "b661378e-8962-11e7-b8af-70106fb01274", "b65dc85d-8962-11e7-b8af-70106fb01274", "b65c9e23-8962-11e7-b8af-70106fb01274", "b65abd7e-8962-11e7-b8af-70106fb01274", "b64f5c07-8962-11e7-b8af-70106fb01274", "b64e7ea7-8962-11e7-b8af-70106fb01274", "b64737f9-8962-11e7-b8af-70106fb01274", "b66c0a8f-8962-11e7-b8af-70106fb01274", "b666b9cc-8962-11e7-b8af-70106fb01274", "b66511f6-8962-11e7-b8af-70106fb01274", "b6606097-8962-11e7-b8af-70106fb01274", "b65f755e-8962-11e7-b8af-70106fb01274", "b65a6dc6-8962-11e7-b8af-70106fb01274", "b64e5406-8962-11e7-b8af-70106fb01274", "b64565a3-8962-11e7-b8af-70106fb01274", "b662bad4-8962-11e7-b8af-70106fb01274", "b65bf0bf-8962-11e7-b8af-70106fb01274", "b6586ae6-8962-11e7-b8af-70106fb01274", "b64f3ead-8962-11e7-b8af-70106fb01274", "b64f041a-8962-11e7-b8af-70106fb01274", "b646ebc9-8962-11e7-b8af-70106fb01274", "b66baece-8962-11e7-b8af-70106fb01274", "b669350c-8962-11e7-b8af-70106fb01274", "b666b46f-8962-11e7-b8af-70106fb01274", "b65c3f04-8962-11e7-b8af-70106fb01274", "b65a1db5-8962-11e7-b8af-70106fb01274", "b6583943-8962-11e7-b8af-70106fb01274", "b6579d3b-8962-11e7-b8af-70106fb01274", "b656bfb6-8962-11e7-b8af-70106fb01274", "b6460283-8962-11e7-b8af-70106fb01274", "b66931db-8962-11e7-b8af-70106fb01274", "b65fcb84-8962-11e7-b8af-70106fb01274", "b65af874-8962-11e7-b8af-70106fb01274", "b65ad958-8962-11e7-b8af-70106fb01274", "b650fe42-8962-11e7-b8af-70106fb01274", "b64fc1c0-8962-11e7-b8af-70106fb01274", "b66aecb5-8962-11e7-b8af-70106fb01274", "b667e0a7-8962-11e7-b8af-70106fb01274", "b660da67-8962-11e7-b8af-70106fb01274", "b65be6d2-8962-11e7-b8af-70106fb01274", "b65be22c-8962-11e7-b8af-70106fb01274", "b65351a5-8962-11e7-b8af-70106fb01274", "b66a4462-8962-11e7-b8af-70106fb01274", "b66a1b3c-8962-11e7-b8af-70106fb01274", "b65aca79-8962-11e7-b8af-70106fb01274", "b658d870-8962-11e7-b8af-70106fb01274", "b6504095-8962-11e7-b8af-70106fb01274", "b646497d-8962-11e7-b8af-70106fb01274", "b646382b-8962-11e7-b8af-70106fb01274", "b66af6e5-8962-11e7-b8af-70106fb01274", "b667046c-8962-11e7-b8af-70106fb01274", "b65c35fb-8962-11e7-b8af-70106fb01274", "b6524966-8962-11e7-b8af-70106fb01274", "b6461bea-8962-11e7-b8af-70106fb01274", "b66ca2d7-8962-11e7-b8af-70106fb01274", "b66bda21-8962-11e7-b8af-70106fb01274", "b6616082-8962-11e7-b8af-70106fb01274", "b65de8c3-8962-11e7-b8af-70106fb01274", "b658eba3-8962-11e7-b8af-70106fb01274", "b6567414-8962-11e7-b8af-70106fb01274", "b64de0b7-8962-11e7-b8af-70106fb01274", "b64794ed-8962-11e7-b8af-70106fb01274", "b6471cc4-8962-11e7-b8af-70106fb01274", "b646e717-8962-11e7-b8af-70106fb01274", "b6467290-8962-11e7-b8af-70106fb01274", "b6459583-8962-11e7-b8af-70106fb01274", "b66dc6f4-8962-11e7-b8af-70106fb01274", "b6650201-8962-11e7-b8af-70106fb01274", "b65e3381-8962-11e7-b8af-70106fb01274", "b65b66c3-8962-11e7-b8af-70106fb01274", "b65b13e7-8962-11e7-b8af-70106fb01274", "b652716b-8962-11e7-b8af-70106fb01274", "b65040cc-8962-11e7-b8af-70106fb01274", "b6479a55-8962-11e7-b8af-70106fb01274", "b6471d04-8962-11e7-b8af-70106fb01274", "b646716c-8962-11e7-b8af-70106fb01274", "b66a7b39-8962-11e7-b8af-70106fb01274", "b6685cbf-8962-11e7-b8af-70106fb01274", "b663e8e0-8962-11e7-b8af-70106fb01274", "b663b694-8962-11e7-b8af-70106fb01274", "b66390cb-8962-11e7-b8af-70106fb01274", "b6637237-8962-11e7-b8af-70106fb01274", "b65f6bb7-8962-11e7-b8af-70106fb01274", "b65d1baf-8962-11e7-b8af-70106fb01274", "b6590ee9-8962-11e7-b8af-70106fb01274", "b657d457-8962-11e7-b8af-70106fb01274", "b6505820-8962-11e7-b8af-70106fb01274", "b6461fe9-8962-11e7-b8af-70106fb01274", "b66ad56d-8962-11e7-b8af-70106fb01274", "b66abbcf-8962-11e7-b8af-70106fb01274", "b665cd0a-8962-11e7-b8af-70106fb01274", "b662ad3e-8962-11e7-b8af-70106fb01274", "b6600f55-8962-11e7-b8af-70106fb01274", "b65cf96e-8962-11e7-b8af-70106fb01274", "b65b6b57-8962-11e7-b8af-70106fb01274", "b65a1c63-8962-11e7-b8af-70106fb01274", "b6460530-8962-11e7-b8af-70106fb01274", "b66a190f-8962-11e7-b8af-70106fb01274", "b664db5f-8962-11e7-b8af-70106fb01274", "b65f41dc-8962-11e7-b8af-70106fb01274", "b65e18cb-8962-11e7-b8af-70106fb01274", "b65c28a3-8962-11e7-b8af-70106fb01274", "b65c2129-8962-11e7-b8af-70106fb01274", "b64f1d01-8962-11e7-b8af-70106fb01274", "b64e27bd-8962-11e7-b8af-70106fb01274", "b6476a05-8962-11e7-b8af-70106fb01274", "b646298f-8962-11e7-b8af-70106fb01274", "b66da6ec-8962-11e7-b8af-70106fb01274", "b66ca009-8962-11e7-b8af-70106fb01274", "b66a52c8-8962-11e7-b8af-70106fb01274", "b667260c-8962-11e7-b8af-70106fb01274", "b66660de-8962-11e7-b8af-70106fb01274", "b664425d-8962-11e7-b8af-70106fb01274", "b6633cb6-8962-11e7-b8af-70106fb01274", "b6628a4c-8962-11e7-b8af-70106fb01274", "b65f8756-8962-11e7-b8af-70106fb01274", "b6591a72-8962-11e7-b8af-70106fb01274", "b64dfbc5-8962-11e7-b8af-70106fb01274", "b66dbcec-8962-11e7-b8af-70106fb01274", "b668dfb8-8962-11e7-b8af-70106fb01274", "b665da0b-8962-11e7-b8af-70106fb01274", "b6658009-8962-11e7-b8af-70106fb01274", "b65b2a78-8962-11e7-b8af-70106fb01274", "b65a5582-8962-11e7-b8af-70106fb01274", "b659869b-8962-11e7-b8af-70106fb01274", "b64dedc3-8962-11e7-b8af-70106fb01274", "b64672e6-8962-11e7-b8af-70106fb01274", "b66d5a4b-8962-11e7-b8af-70106fb01274", "b66ba556-8962-11e7-b8af-70106fb01274", "b66a732d-8962-11e7-b8af-70106fb01274", "b65abeba-8962-11e7-b8af-70106fb01274", "b6455717-8962-11e7-b8af-70106fb01274", "b66e5157-8962-11e7-b8af-70106fb01274", "b66d08ae-8962-11e7-b8af-70106fb01274", "b663d4c2-8962-11e7-b8af-70106fb01274", "b6583381-8962-11e7-b8af-70106fb01274", "b6515ef1-8962-11e7-b8af-70106fb01274", "b645755b-8962-11e7-b8af-70106fb01274", "b645489c-8962-11e7-b8af-70106fb01274", "b66dd17a-8962-11e7-b8af-70106fb01274", "b6658574-8962-11e7-b8af-70106fb01274", "b6612cf7-8962-11e7-b8af-70106fb01274", "b65a6732-8962-11e7-b8af-70106fb01274", "b6465953-8962-11e7-b8af-70106fb01274", "b66cf419-8962-11e7-b8af-70106fb01274", "b668d812-8962-11e7-b8af-70106fb01274", "b666431d-8962-11e7-b8af-70106fb01274", "b6642639-8962-11e7-b8af-70106fb01274", "b663af11-8962-11e7-b8af-70106fb01274", "b6636794-8962-11e7-b8af-70106fb01274", "b66273d2-8962-11e7-b8af-70106fb01274", "b6625d11-8962-11e7-b8af-70106fb01274", "b65e5292-8962-11e7-b8af-70106fb01274", "b659e291-8962-11e7-b8af-70106fb01274", "b64f625c-8962-11e7-b8af-70106fb01274", "b6477630-8962-11e7-b8af-70106fb01274", "b6650180-8962-11e7-b8af-70106fb01274", "b662d0ac-8962-11e7-b8af-70106fb01274", "b661b1b9-8962-11e7-b8af-70106fb01274", "b65b6bc8-8962-11e7-b8af-70106fb01274", "b653585d-8962-11e7-b8af-70106fb01274", "b64e22b9-8962-11e7-b8af-70106fb01274", "b66dd2f5-8962-11e7-b8af-70106fb01274", "b66d7613-8962-11e7-b8af-70106fb01274", "b66d57ae-8962-11e7-b8af-70106fb01274", "b66acecc-8962-11e7-b8af-70106fb01274", "b66a1967-8962-11e7-b8af-70106fb01274", "b664cd92-8962-11e7-b8af-70106fb01274", "b6605887-8962-11e7-b8af-70106fb01274", "b66035d4-8962-11e7-b8af-70106fb01274", "b6600e93-8962-11e7-b8af-70106fb01274", "b65f038c-8962-11e7-b8af-70106fb01274", "b65dde2a-8962-11e7-b8af-70106fb01274", "b6460456-8962-11e7-b8af-70106fb01274", "b667b8bb-8962-11e7-b8af-70106fb01274", "b6635fa8-8962-11e7-b8af-70106fb01274", "b661f010-8962-11e7-b8af-70106fb01274", "b6610f7c-8962-11e7-b8af-70106fb01274", "b65e30c3-8962-11e7-b8af-70106fb01274", "b658e905-8962-11e7-b8af-70106fb01274", "b66b891d-8962-11e7-b8af-70106fb01274", "b664e6c0-8962-11e7-b8af-70106fb01274", "b65e48da-8962-11e7-b8af-70106fb01274", "b65dc5f5-8962-11e7-b8af-70106fb01274", "b65ba1f6-8962-11e7-b8af-70106fb01274", "b6584d0d-8962-11e7-b8af-70106fb01274", "b6566df3-8962-11e7-b8af-70106fb01274", "b64f24de-8962-11e7-b8af-70106fb01274", "b66d450f-8962-11e7-b8af-70106fb01274", "b6633c20-8962-11e7-b8af-70106fb01274", "b6627710-8962-11e7-b8af-70106fb01274", "b6585718-8962-11e7-b8af-70106fb01274", "b657b8bf-8962-11e7-b8af-70106fb01274", "b65712d9-8962-11e7-b8af-70106fb01274", "b64595e8-8962-11e7-b8af-70106fb01274", "b66e7bea-8962-11e7-b8af-70106fb01274", "b66b6e8d-8962-11e7-b8af-70106fb01274", "b66a3e76-8962-11e7-b8af-70106fb01274", "b66505db-8962-11e7-b8af-70106fb01274", "b660c727-8962-11e7-b8af-70106fb01274", "b6594059-8962-11e7-b8af-70106fb01274", "b64db3dc-8962-11e7-b8af-70106fb01274", "b6458750-8962-11e7-b8af-70106fb01274", "b64564a5-8962-11e7-b8af-70106fb01274", "b6453f58-8962-11e7-b8af-70106fb01274", "b66cdc5c-8962-11e7-b8af-70106fb01274", "b66b400f-8962-11e7-b8af-70106fb01274", "b664cfdb-8962-11e7-b8af-70106fb01274", "b65fcf9c-8962-11e7-b8af-70106fb01274", "b6474f43-8962-11e7-b8af-70106fb01274", "b6466c47-8962-11e7-b8af-70106fb01274", "b66df05f-8962-11e7-b8af-70106fb01274", "b66d3944-8962-11e7-b8af-70106fb01274", "b662ee19-8962-11e7-b8af-70106fb01274", "b6626109-8962-11e7-b8af-70106fb01274", "b6611bf0-8962-11e7-b8af-70106fb01274", "b65ddaab-8962-11e7-b8af-70106fb01274", "b65b2977-8962-11e7-b8af-70106fb01274", "b65a7fea-8962-11e7-b8af-70106fb01274", "b650607d-8962-11e7-b8af-70106fb01274", "b64e761a-8962-11e7-b8af-70106fb01274", "b64e36b7-8962-11e7-b8af-70106fb01274", "b6470a1a-8962-11e7-b8af-70106fb01274", "b6626fb3-8962-11e7-b8af-70106fb01274", "b659f0fd-8962-11e7-b8af-70106fb01274", "b65603d4-8962-11e7-b8af-70106fb01274", "b65065b8-8962-11e7-b8af-70106fb01274", "b64fa31d-8962-11e7-b8af-70106fb01274", "b64671b1-8962-11e7-b8af-70106fb01274", "a7f20366-f0f9-47a6-ba76-5f913951e99a", "b66abe8f-8962-11e7-b8af-70106fb01274", "b66964c8-8962-11e7-b8af-70106fb01274", "b668b70f-8962-11e7-b8af-70106fb01274", "b66068cd-8962-11e7-b8af-70106fb01274", "b650c218-8962-11e7-b8af-70106fb01274", "b6467918-8962-11e7-b8af-70106fb01274", "b6667952-8962-11e7-b8af-70106fb01274", "b6606266-8962-11e7-b8af-70106fb01274", "b6604586-8962-11e7-b8af-70106fb01274", "b65e62be-8962-11e7-b8af-70106fb01274", "b65e3496-8962-11e7-b8af-70106fb01274", "b65e1a64-8962-11e7-b8af-70106fb01274", "b65c58f0-8962-11e7-b8af-70106fb01274", "b6539387-8962-11e7-b8af-70106fb01274", "b65315c8-8962-11e7-b8af-70106fb01274", "b6516a0f-8962-11e7-b8af-70106fb01274", "b65158d4-8962-11e7-b8af-70106fb01274", "b6511d1f-8962-11e7-b8af-70106fb01274", "b64799b8-8962-11e7-b8af-70106fb01274", "b6699b2c-8962-11e7-b8af-70106fb01274", "b669253e-8962-11e7-b8af-70106fb01274", "b6685fea-8962-11e7-b8af-70106fb01274", "b6680192-8962-11e7-b8af-70106fb01274", "b666b4bc-8962-11e7-b8af-70106fb01274", "b665ebc0-8962-11e7-b8af-70106fb01274", "b65f5887-8962-11e7-b8af-70106fb01274", "b65d0754-8962-11e7-b8af-70106fb01274", "b65c6f62-8962-11e7-b8af-70106fb01274", "b65c0399-8962-11e7-b8af-70106fb01274", "b645d24d-8962-11e7-b8af-70106fb01274", "b645affe-8962-11e7-b8af-70106fb01274", "b66d307a-8962-11e7-b8af-70106fb01274", "b66b8f86-8962-11e7-b8af-70106fb01274", "b6666571-8962-11e7-b8af-70106fb01274", "b664b63e-8962-11e7-b8af-70106fb01274", "b66467fd-8962-11e7-b8af-70106fb01274", "b664641f-8962-11e7-b8af-70106fb01274", "b65fc0d3-8962-11e7-b8af-70106fb01274", "b65bbf14-8962-11e7-b8af-70106fb01274", "b659570c-8962-11e7-b8af-70106fb01274", "b6587b0c-8962-11e7-b8af-70106fb01274", "b6466697-8962-11e7-b8af-70106fb01274", "b66a6ff9-8962-11e7-b8af-70106fb01274", "b662fbee-8962-11e7-b8af-70106fb01274", "b662447c-8962-11e7-b8af-70106fb01274", "b660c571-8962-11e7-b8af-70106fb01274", "b65c0aa6-8962-11e7-b8af-70106fb01274", "b656f7e7-8962-11e7-b8af-70106fb01274", "b652ff3b-8962-11e7-b8af-70106fb01274", "b64f533c-8962-11e7-b8af-70106fb01274", "b647916f-8962-11e7-b8af-70106fb01274", "b646b1e5-8962-11e7-b8af-70106fb01274", "b6463f52-8962-11e7-b8af-70106fb01274", "b645850f-8962-11e7-b8af-70106fb01274", "b66b544c-8962-11e7-b8af-70106fb01274", "b66acd2c-8962-11e7-b8af-70106fb01274", "b6646706-8962-11e7-b8af-70106fb01274", "b65ef931-8962-11e7-b8af-70106fb01274", "b65e728d-8962-11e7-b8af-70106fb01274", "b65a2229-8962-11e7-b8af-70106fb01274", "b658acd6-8962-11e7-b8af-70106fb01274", "b656ffa7-8962-11e7-b8af-70106fb01274", "b6567f69-8962-11e7-b8af-70106fb01274", "b6464dce-8962-11e7-b8af-70106fb01274", "b6463fa9-8962-11e7-b8af-70106fb01274", "b645c9b5-8962-11e7-b8af-70106fb01274", "b645ad5f-8962-11e7-b8af-70106fb01274", "b6459d46-8962-11e7-b8af-70106fb01274", "b6457683-8962-11e7-b8af-70106fb01274", "b6455821-8962-11e7-b8af-70106fb01274", "b66a9d43-8962-11e7-b8af-70106fb01274", "b66a278d-8962-11e7-b8af-70106fb01274", "b66649a9-8962-11e7-b8af-70106fb01274", "b663f464-8962-11e7-b8af-70106fb01274", "b66301e7-8962-11e7-b8af-70106fb01274", "b6606344-8962-11e7-b8af-70106fb01274", "b6603cae-8962-11e7-b8af-70106fb01274", "b65bc5a6-8962-11e7-b8af-70106fb01274", "b65764c7-8962-11e7-b8af-70106fb01274", "b656cb03-8962-11e7-b8af-70106fb01274", "b652e760-8962-11e7-b8af-70106fb01274", "b6479b66-8962-11e7-b8af-70106fb01274", "b6476e3a-8962-11e7-b8af-70106fb01274", "b645bcfa-8962-11e7-b8af-70106fb01274", "b66e65f8-8962-11e7-b8af-70106fb01274", "b66a042a-8962-11e7-b8af-70106fb01274", "b65e4d98-8962-11e7-b8af-70106fb01274", "b65d18c6-8962-11e7-b8af-70106fb01274", "b65ce9fb-8962-11e7-b8af-70106fb01274", "b659818c-8962-11e7-b8af-70106fb01274", "b65309a4-8962-11e7-b8af-70106fb01274", "b645d83a-8962-11e7-b8af-70106fb01274", "b645d62c-8962-11e7-b8af-70106fb01274", "b66b5a7c-8962-11e7-b8af-70106fb01274", "b6695462-8962-11e7-b8af-70106fb01274", "b66892c5-8962-11e7-b8af-70106fb01274", "b66695b2-8962-11e7-b8af-70106fb01274", "b662d68b-8962-11e7-b8af-70106fb01274", "b65fe42c-8962-11e7-b8af-70106fb01274", "b65e3332-8962-11e7-b8af-70106fb01274", "b65c6d59-8962-11e7-b8af-70106fb01274", "b65c17ab-8962-11e7-b8af-70106fb01274", "b6590f20-8962-11e7-b8af-70106fb01274", "b658d4e3-8962-11e7-b8af-70106fb01274", "b6576a5a-8962-11e7-b8af-70106fb01274", "b656864f-8962-11e7-b8af-70106fb01274", "b64e624b-8962-11e7-b8af-70106fb01274", "b6474c3f-8962-11e7-b8af-70106fb01274", "b6454469-8962-11e7-b8af-70106fb01274", "a7e2030b-f7c9-4ae2-8fb9-219629bc7646", "b66dc4fe-8962-11e7-b8af-70106fb01274", "b66cd653-8962-11e7-b8af-70106fb01274", "b66c6bd6-8962-11e7-b8af-70106fb01274", "b66a516d-8962-11e7-b8af-70106fb01274", "b66729d4-8962-11e7-b8af-70106fb01274", "b6663494-8962-11e7-b8af-70106fb01274", "b664d9d4-8962-11e7-b8af-70106fb01274", "b664a563-8962-11e7-b8af-70106fb01274", "b66425ef-8962-11e7-b8af-70106fb01274", "b661b2c0-8962-11e7-b8af-70106fb01274", "b65e20dd-8962-11e7-b8af-70106fb01274", "b65d308f-8962-11e7-b8af-70106fb01274", "b65b2e1a-8962-11e7-b8af-70106fb01274", "b65abe1b-8962-11e7-b8af-70106fb01274", "b6585bf7-8962-11e7-b8af-70106fb01274", "b6510c97-8962-11e7-b8af-70106fb01274", "b64ddf66-8962-11e7-b8af-70106fb01274", "b6476b36-8962-11e7-b8af-70106fb01274", "b6473f35-8962-11e7-b8af-70106fb01274", "b6471452-8962-11e7-b8af-70106fb01274", "b646f236-8962-11e7-b8af-70106fb01274", "b646b023-8962-11e7-b8af-70106fb01274", "b64634df-8962-11e7-b8af-70106fb01274", "b645fb12-8962-11e7-b8af-70106fb01274", "a7eb0277-5e6b-4cc7-8b14-601e91e67f8b", "b66de2f3-8962-11e7-b8af-70106fb01274", "b669ca2b-8962-11e7-b8af-70106fb01274", "b668f954-8962-11e7-b8af-70106fb01274", "b6685c0f-8962-11e7-b8af-70106fb01274", "b6681be1-8962-11e7-b8af-70106fb01274", "b66106e6-8962-11e7-b8af-70106fb01274", "b65b4b80-8962-11e7-b8af-70106fb01274", "b64fb214-8962-11e7-b8af-70106fb01274", "b6474d16-8962-11e7-b8af-70106fb01274", "b6473786-8962-11e7-b8af-70106fb01274", "b6458d58-8962-11e7-b8af-70106fb01274", "b66e5260-8962-11e7-b8af-70106fb01274", "b66b725c-8962-11e7-b8af-70106fb01274", "b66b47c3-8962-11e7-b8af-70106fb01274", "b664b9d9-8962-11e7-b8af-70106fb01274", "b6628ac6-8962-11e7-b8af-70106fb01274", "b661ff20-8962-11e7-b8af-70106fb01274", "b661ebe5-8962-11e7-b8af-70106fb01274", "b661ea25-8962-11e7-b8af-70106fb01274", "b660a7e7-8962-11e7-b8af-70106fb01274", "b6607e66-8962-11e7-b8af-70106fb01274", "b65dd11b-8962-11e7-b8af-70106fb01274", "b65d5932-8962-11e7-b8af-70106fb01274", "b65d4314-8962-11e7-b8af-70106fb01274", "b65cc0ee-8962-11e7-b8af-70106fb01274", "b65c5a82-8962-11e7-b8af-70106fb01274", "b65b62d0-8962-11e7-b8af-70106fb01274", "b652e2d7-8962-11e7-b8af-70106fb01274", "b6463fd9-8962-11e7-b8af-70106fb01274", "b6460df7-8962-11e7-b8af-70106fb01274", "b66e3c32-8962-11e7-b8af-70106fb01274", "b66dda2f-8962-11e7-b8af-70106fb01274", "b66a48b1-8962-11e7-b8af-70106fb01274", "b66736d2-8962-11e7-b8af-70106fb01274", "b661296a-8962-11e7-b8af-70106fb01274", "b65efdcd-8962-11e7-b8af-70106fb01274", "b65bb04e-8962-11e7-b8af-70106fb01274", "b65a8b31-8962-11e7-b8af-70106fb01274", "b657d2dd-8962-11e7-b8af-70106fb01274", "b650574a-8962-11e7-b8af-70106fb01274", "b64fccc8-8962-11e7-b8af-70106fb01274", "b66ba31d-8962-11e7-b8af-70106fb01274", "b66acce6-8962-11e7-b8af-70106fb01274", "b669dabb-8962-11e7-b8af-70106fb01274", "b669419e-8962-11e7-b8af-70106fb01274", "b666df50-8962-11e7-b8af-70106fb01274", "b666d2d9-8962-11e7-b8af-70106fb01274", "b664bba9-8962-11e7-b8af-70106fb01274", "b664babe-8962-11e7-b8af-70106fb01274", "b65ec3a8-8962-11e7-b8af-70106fb01274", "b65d926e-8962-11e7-b8af-70106fb01274", "b65c4393-8962-11e7-b8af-70106fb01274", "b6576dbc-8962-11e7-b8af-70106fb01274", "b65636e6-8962-11e7-b8af-70106fb01274", "b64724af-8962-11e7-b8af-70106fb01274", "b646fdc6-8962-11e7-b8af-70106fb01274", "b66d85c2-8962-11e7-b8af-70106fb01274", "b66d4567-8962-11e7-b8af-70106fb01274", "b6650427-8962-11e7-b8af-70106fb01274", "b6628b81-8962-11e7-b8af-70106fb01274", "b65cc0a9-8962-11e7-b8af-70106fb01274", "b65ba246-8962-11e7-b8af-70106fb01274", "b65aaecc-8962-11e7-b8af-70106fb01274", "b650a97d-8962-11e7-b8af-70106fb01274", "b64f6a39-8962-11e7-b8af-70106fb01274", "b64ed307-8962-11e7-b8af-70106fb01274", "b647811d-8962-11e7-b8af-70106fb01274", "b646cf43-8962-11e7-b8af-70106fb01274", "b64541ed-8962-11e7-b8af-70106fb01274", "b6452e82-8962-11e7-b8af-70106fb01274", "b66dd7d5-8962-11e7-b8af-70106fb01274", "b668e93c-8962-11e7-b8af-70106fb01274", "b664c043-8962-11e7-b8af-70106fb01274", "b6625cce-8962-11e7-b8af-70106fb01274", "b6602f9b-8962-11e7-b8af-70106fb01274", "b65fa681-8962-11e7-b8af-70106fb01274", "b65dbef6-8962-11e7-b8af-70106fb01274", "b659b049-8962-11e7-b8af-70106fb01274", "b659386b-8962-11e7-b8af-70106fb01274", "b6526903-8962-11e7-b8af-70106fb01274", "b651afba-8962-11e7-b8af-70106fb01274", "b6503f16-8962-11e7-b8af-70106fb01274", "b64e7af8-8962-11e7-b8af-70106fb01274", "b646d18e-8962-11e7-b8af-70106fb01274", "b645b4ec-8962-11e7-b8af-70106fb01274", "b64574ae-8962-11e7-b8af-70106fb01274", "b6454a70-8962-11e7-b8af-70106fb01274", "b66d6dab-8962-11e7-b8af-70106fb01274", "b66bea75-8962-11e7-b8af-70106fb01274", "b66643fe-8962-11e7-b8af-70106fb01274", "b6657bf6-8962-11e7-b8af-70106fb01274", "b665194f-8962-11e7-b8af-70106fb01274", "b663f28e-8962-11e7-b8af-70106fb01274", "b65fd651-8962-11e7-b8af-70106fb01274", "b65e0d3e-8962-11e7-b8af-70106fb01274", "b65d4a8b-8962-11e7-b8af-70106fb01274", "b65cc204-8962-11e7-b8af-70106fb01274", "b65c2ff0-8962-11e7-b8af-70106fb01274", "b6582d7a-8962-11e7-b8af-70106fb01274", "b6522476-8962-11e7-b8af-70106fb01274", "b650767b-8962-11e7-b8af-70106fb01274", "b6504640-8962-11e7-b8af-70106fb01274", "b64fb00f-8962-11e7-b8af-70106fb01274", "b646f7bb-8962-11e7-b8af-70106fb01274", "b6465b9a-8962-11e7-b8af-70106fb01274", "b64638e9-8962-11e7-b8af-70106fb01274", "b646100a-8962-11e7-b8af-70106fb01274", "b6459789-8962-11e7-b8af-70106fb01274", "b66c4383-8962-11e7-b8af-70106fb01274", "b66a394a-8962-11e7-b8af-70106fb01274", "b664fb0d-8962-11e7-b8af-70106fb01274", "b6625900-8962-11e7-b8af-70106fb01274", "b6601fae-8962-11e7-b8af-70106fb01274", "b65c7117-8962-11e7-b8af-70106fb01274", "b65c23e0-8962-11e7-b8af-70106fb01274", "b6599e40-8962-11e7-b8af-70106fb01274", "b6512420-8962-11e7-b8af-70106fb01274", "b64d90c7-8962-11e7-b8af-70106fb01274", "b646d07f-8962-11e7-b8af-70106fb01274", "b6463b66-8962-11e7-b8af-70106fb01274", "b6462a11-8962-11e7-b8af-70106fb01274", "b6452588-8962-11e7-b8af-70106fb01274", "b6648713-8962-11e7-b8af-70106fb01274", "b662e0ad-8962-11e7-b8af-70106fb01274", "b6619382-8962-11e7-b8af-70106fb01274", "b65e6b73-8962-11e7-b8af-70106fb01274", "b65b432d-8962-11e7-b8af-70106fb01274", "b651932a-8962-11e7-b8af-70106fb01274", "b64ee349-8962-11e7-b8af-70106fb01274", "b64dcf4d-8962-11e7-b8af-70106fb01274", "b646031b-8962-11e7-b8af-70106fb01274", "b645e4a2-8962-11e7-b8af-70106fb01274", "b645b067-8962-11e7-b8af-70106fb01274", "b6457e2a-8962-11e7-b8af-70106fb01274", "b66ce2ec-8962-11e7-b8af-70106fb01274", "b66b0fb8-8962-11e7-b8af-70106fb01274", "b666945d-8962-11e7-b8af-70106fb01274", "b6668aad-8962-11e7-b8af-70106fb01274", "b6651f5f-8962-11e7-b8af-70106fb01274", "b6642eb5-8962-11e7-b8af-70106fb01274", "b663aa43-8962-11e7-b8af-70106fb01274", "b6620407-8962-11e7-b8af-70106fb01274", "b661e4f1-8962-11e7-b8af-70106fb01274", "b65fd285-8962-11e7-b8af-70106fb01274", "b65d9e5d-8962-11e7-b8af-70106fb01274", "b65afd1f-8962-11e7-b8af-70106fb01274", "b6594da6-8962-11e7-b8af-70106fb01274", "b657ef56-8962-11e7-b8af-70106fb01274", "b64f7bde-8962-11e7-b8af-70106fb01274", "b64f2711-8962-11e7-b8af-70106fb01274", "b64dbc53-8962-11e7-b8af-70106fb01274", "b6476c1c-8962-11e7-b8af-70106fb01274", "b6470827-8962-11e7-b8af-70106fb01274", "b646a64a-8962-11e7-b8af-70106fb01274", "b645aefd-8962-11e7-b8af-70106fb01274", "b66ba493-8962-11e7-b8af-70106fb01274", "b6646592-8962-11e7-b8af-70106fb01274", "b663f4b4-8962-11e7-b8af-70106fb01274", "b6630f5e-8962-11e7-b8af-70106fb01274", "b65f9187-8962-11e7-b8af-70106fb01274", "b65f497b-8962-11e7-b8af-70106fb01274", "b65da1d4-8962-11e7-b8af-70106fb01274", "b65cf112-8962-11e7-b8af-70106fb01274", "b65b566c-8962-11e7-b8af-70106fb01274", "b65b51db-8962-11e7-b8af-70106fb01274", "b65a92d0-8962-11e7-b8af-70106fb01274", "b65a2d57-8962-11e7-b8af-70106fb01274", "b657e702-8962-11e7-b8af-70106fb01274", "b657bc08-8962-11e7-b8af-70106fb01274", "b650cfc1-8962-11e7-b8af-70106fb01274", "b64e689d-8962-11e7-b8af-70106fb01274", "b646a451-8962-11e7-b8af-70106fb01274", "b645c8e5-8962-11e7-b8af-70106fb01274", "b66e51dd-8962-11e7-b8af-70106fb01274", "b66dbb34-8962-11e7-b8af-70106fb01274", "b66c8d53-8962-11e7-b8af-70106fb01274", "b66b0772-8962-11e7-b8af-70106fb01274", "b66934c5-8962-11e7-b8af-70106fb01274", "b664447b-8962-11e7-b8af-70106fb01274", "b663e832-8962-11e7-b8af-70106fb01274", "b65dc4fa-8962-11e7-b8af-70106fb01274", "b65cb049-8962-11e7-b8af-70106fb01274", "b65c9f5e-8962-11e7-b8af-70106fb01274", "b65bb2bc-8962-11e7-b8af-70106fb01274", "b65abdcc-8962-11e7-b8af-70106fb01274", "b65a5d96-8962-11e7-b8af-70106fb01274", "b658fba0-8962-11e7-b8af-70106fb01274", "b657c854-8962-11e7-b8af-70106fb01274", "b65691fe-8962-11e7-b8af-70106fb01274", "b652c4c9-8962-11e7-b8af-70106fb01274", "b652552f-8962-11e7-b8af-70106fb01274", "b6510109-8962-11e7-b8af-70106fb01274", "b64e149f-8962-11e7-b8af-70106fb01274", "b6470d7c-8962-11e7-b8af-70106fb01274", "b64699d7-8962-11e7-b8af-70106fb01274", "b6467cbc-8962-11e7-b8af-70106fb01274", "b6464303-8962-11e7-b8af-70106fb01274", "b6458ef3-8962-11e7-b8af-70106fb01274", "b64571e0-8962-11e7-b8af-70106fb01274", "b66df53f-8962-11e7-b8af-70106fb01274", "b66bcb32-8962-11e7-b8af-70106fb01274", "b66ba779-8962-11e7-b8af-70106fb01274", "b6686b41-8962-11e7-b8af-70106fb01274", "b66662ce-8962-11e7-b8af-70106fb01274", "b666208e-8962-11e7-b8af-70106fb01274", "b66391f7-8962-11e7-b8af-70106fb01274", "b663637b-8962-11e7-b8af-70106fb01274", "b6635002-8962-11e7-b8af-70106fb01274", "b66341f4-8962-11e7-b8af-70106fb01274", "b66337e0-8962-11e7-b8af-70106fb01274", "b6633467-8962-11e7-b8af-70106fb01274", "b6628d0a-8962-11e7-b8af-70106fb01274", "b6628149-8962-11e7-b8af-70106fb01274", "b65b7b97-8962-11e7-b8af-70106fb01274", "b6579997-8962-11e7-b8af-70106fb01274", "b656b2af-8962-11e7-b8af-70106fb01274", "b6564f71-8962-11e7-b8af-70106fb01274", "b6502875-8962-11e7-b8af-70106fb01274", "b64eb25e-8962-11e7-b8af-70106fb01274", "b64e836f-8962-11e7-b8af-70106fb01274", "b64df25b-8962-11e7-b8af-70106fb01274", "b6479503-8962-11e7-b8af-70106fb01274", "b64771eb-8962-11e7-b8af-70106fb01274", "b646f6f1-8962-11e7-b8af-70106fb01274", "b6460d55-8962-11e7-b8af-70106fb01274", "b645d02b-8962-11e7-b8af-70106fb01274", "b645c159-8962-11e7-b8af-70106fb01274", "b66e5e7d-8962-11e7-b8af-70106fb01274", "b66e25bf-8962-11e7-b8af-70106fb01274", "b66e0b71-8962-11e7-b8af-70106fb01274", "b668b218-8962-11e7-b8af-70106fb01274", "b6679135-8962-11e7-b8af-70106fb01274", "b6667718-8962-11e7-b8af-70106fb01274", "b665cfca-8962-11e7-b8af-70106fb01274", "b6637d71-8962-11e7-b8af-70106fb01274", "b660b9ce-8962-11e7-b8af-70106fb01274", "b66045b8-8962-11e7-b8af-70106fb01274", "b65eb599-8962-11e7-b8af-70106fb01274", "b65c7c0e-8962-11e7-b8af-70106fb01274", "b65c4ef0-8962-11e7-b8af-70106fb01274", "b65b3d28-8962-11e7-b8af-70106fb01274", "b65a5444-8962-11e7-b8af-70106fb01274", "b656b9c0-8962-11e7-b8af-70106fb01274", "b647a36a-8962-11e7-b8af-70106fb01274", "b647a0f7-8962-11e7-b8af-70106fb01274", "b646f895-8962-11e7-b8af-70106fb01274", "b6460b08-8962-11e7-b8af-70106fb01274", "b645ebb0-8962-11e7-b8af-70106fb01274", "b645b5a4-8962-11e7-b8af-70106fb01274", "b6456d37-8962-11e7-b8af-70106fb01274", "b6456787-8962-11e7-b8af-70106fb01274", "b64551fc-8962-11e7-b8af-70106fb01274", "b66c4331-8962-11e7-b8af-70106fb01274", "b66b9f1c-8962-11e7-b8af-70106fb01274", "b66b7145-8962-11e7-b8af-70106fb01274", "b6692025-8962-11e7-b8af-70106fb01274", "b6658040-8962-11e7-b8af-70106fb01274", "b664f2f8-8962-11e7-b8af-70106fb01274", "b6645429-8962-11e7-b8af-70106fb01274", "b663cb5f-8962-11e7-b8af-70106fb01274", "b66316bd-8962-11e7-b8af-70106fb01274", "b662da00-8962-11e7-b8af-70106fb01274", "b660fe41-8962-11e7-b8af-70106fb01274", "b660ce7a-8962-11e7-b8af-70106fb01274", "b66045eb-8962-11e7-b8af-70106fb01274", "b65837da-8962-11e7-b8af-70106fb01274", "b6568a63-8962-11e7-b8af-70106fb01274", "b64fad52-8962-11e7-b8af-70106fb01274", "b64d899c-8962-11e7-b8af-70106fb01274", "b646bd70-8962-11e7-b8af-70106fb01274", "b6468c51-8962-11e7-b8af-70106fb01274", "b6467279-8962-11e7-b8af-70106fb01274", "b6465598-8962-11e7-b8af-70106fb01274", "b645aed4-8962-11e7-b8af-70106fb01274", "b66d5f8c-8962-11e7-b8af-70106fb01274", "b66aa18f-8962-11e7-b8af-70106fb01274", "b6693e51-8962-11e7-b8af-70106fb01274", "b6671b8f-8962-11e7-b8af-70106fb01274", "b663928f-8962-11e7-b8af-70106fb01274", "b6600e14-8962-11e7-b8af-70106fb01274", "b65ed3d2-8962-11e7-b8af-70106fb01274", "b65a6b09-8962-11e7-b8af-70106fb01274", "b659e578-8962-11e7-b8af-70106fb01274", "b6595cfc-8962-11e7-b8af-70106fb01274", "b652bdc4-8962-11e7-b8af-70106fb01274", "b652979c-8962-11e7-b8af-70106fb01274", "b65239eb-8962-11e7-b8af-70106fb01274", "b6521f86-8962-11e7-b8af-70106fb01274", "b647af25-8962-11e7-b8af-70106fb01274", "b64761be-8962-11e7-b8af-70106fb01274", "b647436a-8962-11e7-b8af-70106fb01274", "b645efda-8962-11e7-b8af-70106fb01274", "b645d179-8962-11e7-b8af-70106fb01274", "b645a137-8962-11e7-b8af-70106fb01274", "b66cf375-8962-11e7-b8af-70106fb01274", "b66cdec2-8962-11e7-b8af-70106fb01274", "b66c79ee-8962-11e7-b8af-70106fb01274", "b66b4ae1-8962-11e7-b8af-70106fb01274", "b66a29ff-8962-11e7-b8af-70106fb01274", "b665cd59-8962-11e7-b8af-70106fb01274", "b665732e-8962-11e7-b8af-70106fb01274", "b662fd56-8962-11e7-b8af-70106fb01274", "b66281e3-8962-11e7-b8af-70106fb01274", "b661a382-8962-11e7-b8af-70106fb01274", "b660f86c-8962-11e7-b8af-70106fb01274", "b65fe2d8-8962-11e7-b8af-70106fb01274", "b65fcc0f-8962-11e7-b8af-70106fb01274", "b65eedb3-8962-11e7-b8af-70106fb01274", "b65eda82-8962-11e7-b8af-70106fb01274", "b65cc624-8962-11e7-b8af-70106fb01274", "b65bdb24-8962-11e7-b8af-70106fb01274", "b65b7ab6-8962-11e7-b8af-70106fb01274", "b658f08f-8962-11e7-b8af-70106fb01274", "b658b1a0-8962-11e7-b8af-70106fb01274", "b65786c0-8962-11e7-b8af-70106fb01274", "b6569451-8962-11e7-b8af-70106fb01274", "b651789d-8962-11e7-b8af-70106fb01274", "b646e9c5-8962-11e7-b8af-70106fb01274", "b6463869-8962-11e7-b8af-70106fb01274", "b645f629-8962-11e7-b8af-70106fb01274", "b66c82f3-8962-11e7-b8af-70106fb01274", "b66be82f-8962-11e7-b8af-70106fb01274", "b66af21a-8962-11e7-b8af-70106fb01274", "b66adf7f-8962-11e7-b8af-70106fb01274", "b668ea09-8962-11e7-b8af-70106fb01274", "b6680492-8962-11e7-b8af-70106fb01274", "b6674a32-8962-11e7-b8af-70106fb01274", "b6647aab-8962-11e7-b8af-70106fb01274", "b6643c4a-8962-11e7-b8af-70106fb01274", "b664306c-8962-11e7-b8af-70106fb01274", "b6628844-8962-11e7-b8af-70106fb01274", "b6625dde-8962-11e7-b8af-70106fb01274", "b6625086-8962-11e7-b8af-70106fb01274", "b660c780-8962-11e7-b8af-70106fb01274", "b660abee-8962-11e7-b8af-70106fb01274", "b6609417-8962-11e7-b8af-70106fb01274", "b66048e0-8962-11e7-b8af-70106fb01274", "b65f1854-8962-11e7-b8af-70106fb01274", "b65c4afd-8962-11e7-b8af-70106fb01274", "b65b0079-8962-11e7-b8af-70106fb01274", "b6580e5c-8962-11e7-b8af-70106fb01274", "b6580d24-8962-11e7-b8af-70106fb01274", "b656e8a4-8962-11e7-b8af-70106fb01274", "b6562dfd-8962-11e7-b8af-70106fb01274", "b653b8be-8962-11e7-b8af-70106fb01274", "b652f6a9-8962-11e7-b8af-70106fb01274", "b6528649-8962-11e7-b8af-70106fb01274", "b6508997-8962-11e7-b8af-70106fb01274", "b6476b92-8962-11e7-b8af-70106fb01274", "b6467239-8962-11e7-b8af-70106fb01274", "b6459761-8962-11e7-b8af-70106fb01274", "b64585ab-8962-11e7-b8af-70106fb01274", "b645803d-8962-11e7-b8af-70106fb01274", "b6457a86-8962-11e7-b8af-70106fb01274", "b6453561-8962-11e7-b8af-70106fb01274", "b66dbdff-8962-11e7-b8af-70106fb01274", "b66cb87b-8962-11e7-b8af-70106fb01274", "b66baa5a-8962-11e7-b8af-70106fb01274", "b66aed78-8962-11e7-b8af-70106fb01274", "b66a930d-8962-11e7-b8af-70106fb01274", "b66a29a5-8962-11e7-b8af-70106fb01274", "b669a8e7-8962-11e7-b8af-70106fb01274", "b6670e97-8962-11e7-b8af-70106fb01274", "b666a628-8962-11e7-b8af-70106fb01274", "b66602ab-8962-11e7-b8af-70106fb01274", "b663f002-8962-11e7-b8af-70106fb01274", "b663b641-8962-11e7-b8af-70106fb01274", "b6638c62-8962-11e7-b8af-70106fb01274", "b66362e6-8962-11e7-b8af-70106fb01274", "b66319c2-8962-11e7-b8af-70106fb01274", "b66208d4-8962-11e7-b8af-70106fb01274", "b6616adf-8962-11e7-b8af-70106fb01274", "b6611b1f-8962-11e7-b8af-70106fb01274", "b660441d-8962-11e7-b8af-70106fb01274", "b65ffbb3-8962-11e7-b8af-70106fb01274", "b65fa158-8962-11e7-b8af-70106fb01274", "b65bedf1-8962-11e7-b8af-70106fb01274", "b65aa651-8962-11e7-b8af-70106fb01274", "b65a87ca-8962-11e7-b8af-70106fb01274", "b6595b96-8962-11e7-b8af-70106fb01274", "b650afdc-8962-11e7-b8af-70106fb01274", "b64f2ba8-8962-11e7-b8af-70106fb01274", "b647ada0-8962-11e7-b8af-70106fb01274", "b645c715-8962-11e7-b8af-70106fb01274", "b6455f56-8962-11e7-b8af-70106fb01274", "b6453b8c-8962-11e7-b8af-70106fb01274", "b66de5eb-8962-11e7-b8af-70106fb01274", "b667a902-8962-11e7-b8af-70106fb01274", "b6644202-8962-11e7-b8af-70106fb01274", "b6634239-8962-11e7-b8af-70106fb01274", "b6625c0a-8962-11e7-b8af-70106fb01274", "b6620c90-8962-11e7-b8af-70106fb01274", "b660f477-8962-11e7-b8af-70106fb01274", "b660d60a-8962-11e7-b8af-70106fb01274", "b65f4e14-8962-11e7-b8af-70106fb01274", "b65ed89f-8962-11e7-b8af-70106fb01274", "b65ed851-8962-11e7-b8af-70106fb01274", "b65d1dbe-8962-11e7-b8af-70106fb01274", "b65bbb9d-8962-11e7-b8af-70106fb01274", "b65b1f81-8962-11e7-b8af-70106fb01274", "b65b15a0-8962-11e7-b8af-70106fb01274", "b6597d2c-8962-11e7-b8af-70106fb01274", "b6597ad9-8962-11e7-b8af-70106fb01274", "b658abee-8962-11e7-b8af-70106fb01274", "b657c3a2-8962-11e7-b8af-70106fb01274", "b6575942-8962-11e7-b8af-70106fb01274", "b656bc1d-8962-11e7-b8af-70106fb01274", "b6568195-8962-11e7-b8af-70106fb01274", "b652eb43-8962-11e7-b8af-70106fb01274", "b650b5b4-8962-11e7-b8af-70106fb01274", "b65088f7-8962-11e7-b8af-70106fb01274", "b64f8027-8962-11e7-b8af-70106fb01274", "b647323f-8962-11e7-b8af-70106fb01274", "b646cc15-8962-11e7-b8af-70106fb01274", "b6467d97-8962-11e7-b8af-70106fb01274", "b645dc37-8962-11e7-b8af-70106fb01274", "b645d575-8962-11e7-b8af-70106fb01274", "b66e6ef0-8962-11e7-b8af-70106fb01274", "b66ded75-8962-11e7-b8af-70106fb01274", "b66deaf6-8962-11e7-b8af-70106fb01274", "b66be26a-8962-11e7-b8af-70106fb01274", "b66a8c74-8962-11e7-b8af-70106fb01274", "b669af63-8962-11e7-b8af-70106fb01274", "b668911d-8962-11e7-b8af-70106fb01274", "b66594ad-8962-11e7-b8af-70106fb01274", "b664f879-8962-11e7-b8af-70106fb01274", "b6644c9c-8962-11e7-b8af-70106fb01274", "b6634590-8962-11e7-b8af-70106fb01274", "b661a41a-8962-11e7-b8af-70106fb01274", "b6616531-8962-11e7-b8af-70106fb01274", "b661269d-8962-11e7-b8af-70106fb01274", "b660e526-8962-11e7-b8af-70106fb01274", "b6608ae8-8962-11e7-b8af-70106fb01274", "b6604959-8962-11e7-b8af-70106fb01274", "b65f45f7-8962-11e7-b8af-70106fb01274", "b65ebf3a-8962-11e7-b8af-70106fb01274", "b65e7f8c-8962-11e7-b8af-70106fb01274", "b65de53e-8962-11e7-b8af-70106fb01274", "b65dafca-8962-11e7-b8af-70106fb01274", "b65c3cec-8962-11e7-b8af-70106fb01274", "b65ab26c-8962-11e7-b8af-70106fb01274", "b65a879a-8962-11e7-b8af-70106fb01274", "b659d173-8962-11e7-b8af-70106fb01274", "b658e7c0-8962-11e7-b8af-70106fb01274", "b65866c6-8962-11e7-b8af-70106fb01274", "b6584920-8962-11e7-b8af-70106fb01274", "b656feba-8962-11e7-b8af-70106fb01274", "b65310c3-8962-11e7-b8af-70106fb01274", "b652ab4a-8962-11e7-b8af-70106fb01274", "b651aa90-8962-11e7-b8af-70106fb01274", "b64fa463-8962-11e7-b8af-70106fb01274", "b646d5e5-8962-11e7-b8af-70106fb01274", "b645cb94-8962-11e7-b8af-70106fb01274", "b645bcba-8962-11e7-b8af-70106fb01274", "b64581a5-8962-11e7-b8af-70106fb01274", "b6452617-8962-11e7-b8af-70106fb01274", "b66c7e37-8962-11e7-b8af-70106fb01274", "b66b496c-8962-11e7-b8af-70106fb01274", "b66ad087-8962-11e7-b8af-70106fb01274", "b6699b68-8962-11e7-b8af-70106fb01274", "b6692a05-8962-11e7-b8af-70106fb01274", "b6684acd-8962-11e7-b8af-70106fb01274", "b66815e9-8962-11e7-b8af-70106fb01274", "b665286d-8962-11e7-b8af-70106fb01274", "b6651cb9-8962-11e7-b8af-70106fb01274", "b664ba71-8962-11e7-b8af-70106fb01274", "b663ce5c-8962-11e7-b8af-70106fb01274", "b66359e6-8962-11e7-b8af-70106fb01274", "b663207f-8962-11e7-b8af-70106fb01274", "b6625c8c-8962-11e7-b8af-70106fb01274", "b662379e-8962-11e7-b8af-70106fb01274", "b6616775-8962-11e7-b8af-70106fb01274", "b6608b87-8962-11e7-b8af-70106fb01274", "b65f9b19-8962-11e7-b8af-70106fb01274", "b65f6dbe-8962-11e7-b8af-70106fb01274", "b65f3f12-8962-11e7-b8af-70106fb01274", "b65cd16a-8962-11e7-b8af-70106fb01274", "b65c9ede-8962-11e7-b8af-70106fb01274", "b65c30c8-8962-11e7-b8af-70106fb01274", "b65c1988-8962-11e7-b8af-70106fb01274", "b659bcfc-8962-11e7-b8af-70106fb01274", "b6584007-8962-11e7-b8af-70106fb01274", "b6572e66-8962-11e7-b8af-70106fb01274", "b6528884-8962-11e7-b8af-70106fb01274", "b65275f6-8962-11e7-b8af-70106fb01274", "b6515dd4-8962-11e7-b8af-70106fb01274", "b65062f5-8962-11e7-b8af-70106fb01274", "b64f2d76-8962-11e7-b8af-70106fb01274", "b64e3040-8962-11e7-b8af-70106fb01274", "b6471e40-8962-11e7-b8af-70106fb01274", "b6471941-8962-11e7-b8af-70106fb01274", "b647029e-8962-11e7-b8af-70106fb01274", "b646f9f6-8962-11e7-b8af-70106fb01274", "b646ee37-8962-11e7-b8af-70106fb01274", "b646e969-8962-11e7-b8af-70106fb01274", "b646dba4-8962-11e7-b8af-70106fb01274", "b646c1e7-8962-11e7-b8af-70106fb01274", "b6465778-8962-11e7-b8af-70106fb01274", "b646348b-8962-11e7-b8af-70106fb01274", "b645ec89-8962-11e7-b8af-70106fb01274", "b6459a7f-8962-11e7-b8af-70106fb01274", "b6457f92-8962-11e7-b8af-70106fb01274", "b66e4b8a-8962-11e7-b8af-70106fb01274", "b66cb617-8962-11e7-b8af-70106fb01274", "b66c375b-8962-11e7-b8af-70106fb01274", "b66b5651-8962-11e7-b8af-70106fb01274", "b66976b6-8962-11e7-b8af-70106fb01274", "b669048b-8962-11e7-b8af-70106fb01274", "b668de20-8962-11e7-b8af-70106fb01274", "b6675eab-8962-11e7-b8af-70106fb01274", "b6651513-8962-11e7-b8af-70106fb01274", "b66393cc-8962-11e7-b8af-70106fb01274", "b662b4e1-8962-11e7-b8af-70106fb01274", "b66251b5-8962-11e7-b8af-70106fb01274", "b660461d-8962-11e7-b8af-70106fb01274", "b65ea7cb-8962-11e7-b8af-70106fb01274", "b65e50c1-8962-11e7-b8af-70106fb01274", "b65dd874-8962-11e7-b8af-70106fb01274", "b65d0162-8962-11e7-b8af-70106fb01274", "b65c73c3-8962-11e7-b8af-70106fb01274", "b65c0548-8962-11e7-b8af-70106fb01274", "b65bf87b-8962-11e7-b8af-70106fb01274", "b65b3bc8-8962-11e7-b8af-70106fb01274", "b65a08f0-8962-11e7-b8af-70106fb01274", "b65915d2-8962-11e7-b8af-70106fb01274", "b658dc4c-8962-11e7-b8af-70106fb01274", "b658a045-8962-11e7-b8af-70106fb01274", "b658003d-8962-11e7-b8af-70106fb01274", "b656734b-8962-11e7-b8af-70106fb01274", "b6565409-8962-11e7-b8af-70106fb01274", "b652f65a-8962-11e7-b8af-70106fb01274", "b65220ea-8962-11e7-b8af-70106fb01274", "b6512707-8962-11e7-b8af-70106fb01274", "b64fbc93-8962-11e7-b8af-70106fb01274", "b64e09b6-8962-11e7-b8af-70106fb01274", "b647b160-8962-11e7-b8af-70106fb01274", "b64780a9-8962-11e7-b8af-70106fb01274", "b64763a5-8962-11e7-b8af-70106fb01274", "b6473abe-8962-11e7-b8af-70106fb01274", "b646eb45-8962-11e7-b8af-70106fb01274", "b646719c-8962-11e7-b8af-70106fb01274", "b646712d-8962-11e7-b8af-70106fb01274", "b64615d5-8962-11e7-b8af-70106fb01274", "b645e734-8962-11e7-b8af-70106fb01274", "b645d0d2-8962-11e7-b8af-70106fb01274", "b6459612-8962-11e7-b8af-70106fb01274", "b6457533-8962-11e7-b8af-70106fb01274", "b6455e17-8962-11e7-b8af-70106fb01274", "b66e563f-8962-11e7-b8af-70106fb01274", "b66da888-8962-11e7-b8af-70106fb01274", "b66c5371-8962-11e7-b8af-70106fb01274", "b66ade20-8962-11e7-b8af-70106fb01274", "b66a37e4-8962-11e7-b8af-70106fb01274", "b669f4ef-8962-11e7-b8af-70106fb01274", "b669e073-8962-11e7-b8af-70106fb01274", "b6698aa4-8962-11e7-b8af-70106fb01274", "b66936c9-8962-11e7-b8af-70106fb01274", "b668f51c-8962-11e7-b8af-70106fb01274", "b667f1a8-8962-11e7-b8af-70106fb01274", "b6673c15-8962-11e7-b8af-70106fb01274", "b666ada5-8962-11e7-b8af-70106fb01274", "b666a54d-8962-11e7-b8af-70106fb01274", "b666a077-8962-11e7-b8af-70106fb01274", "b6664f37-8962-11e7-b8af-70106fb01274", "b6658e7c-8962-11e7-b8af-70106fb01274", "b6652448-8962-11e7-b8af-70106fb01274", "b664a862-8962-11e7-b8af-70106fb01274", "b663182f-8962-11e7-b8af-70106fb01274", "b662ef6f-8962-11e7-b8af-70106fb01274", "b662c416-8962-11e7-b8af-70106fb01274", "b6629c39-8962-11e7-b8af-70106fb01274", "b661f418-8962-11e7-b8af-70106fb01274", "b661d0e9-8962-11e7-b8af-70106fb01274", "b6618153-8962-11e7-b8af-70106fb01274", "b6617faa-8962-11e7-b8af-70106fb01274", "b66127c8-8962-11e7-b8af-70106fb01274", "b66107fe-8962-11e7-b8af-70106fb01274", "b65ff8b5-8962-11e7-b8af-70106fb01274", "b65f7c03-8962-11e7-b8af-70106fb01274", "b65dfb51-8962-11e7-b8af-70106fb01274", "b65c77a8-8962-11e7-b8af-70106fb01274", "b65c3fd8-8962-11e7-b8af-70106fb01274", "b65b18b1-8962-11e7-b8af-70106fb01274", "b65ad00a-8962-11e7-b8af-70106fb01274", "b6588a5d-8962-11e7-b8af-70106fb01274", "b657355d-8962-11e7-b8af-70106fb01274", "b656c2ec-8962-11e7-b8af-70106fb01274", "b656b854-8962-11e7-b8af-70106fb01274", "b6561f1d-8962-11e7-b8af-70106fb01274", "b6530c17-8962-11e7-b8af-70106fb01274", "b652dd36-8962-11e7-b8af-70106fb01274", "b650313f-8962-11e7-b8af-70106fb01274", "b64fe877-8962-11e7-b8af-70106fb01274", "b64d87fe-8962-11e7-b8af-70106fb01274", "b64797e4-8962-11e7-b8af-70106fb01274", "b64760df-8962-11e7-b8af-70106fb01274", "b64719b9-8962-11e7-b8af-70106fb01274", "b6471788-8962-11e7-b8af-70106fb01274", "b646ec08-8962-11e7-b8af-70106fb01274", "b6467c0e-8962-11e7-b8af-70106fb01274", "b645c66a-8962-11e7-b8af-70106fb01274", "b645c0ef-8962-11e7-b8af-70106fb01274", "b64569c7-8962-11e7-b8af-70106fb01274", "b66e79e4-8962-11e7-b8af-70106fb01274", "b66e55aa-8962-11e7-b8af-70106fb01274", "b66e1a1e-8962-11e7-b8af-70106fb01274", "b66cfbe8-8962-11e7-b8af-70106fb01274", "b66c96e3-8962-11e7-b8af-70106fb01274", "b66bbde3-8962-11e7-b8af-70106fb01274", "b66b6f13-8962-11e7-b8af-70106fb01274", "b66b33e8-8962-11e7-b8af-70106fb01274", "b66b01a4-8962-11e7-b8af-70106fb01274", "b668bd56-8962-11e7-b8af-70106fb01274", "b666ab4d-8962-11e7-b8af-70106fb01274", "b66670a5-8962-11e7-b8af-70106fb01274", "b6660556-8962-11e7-b8af-70106fb01274", "b665209f-8962-11e7-b8af-70106fb01274", "b6634a3f-8962-11e7-b8af-70106fb01274", "b662ddef-8962-11e7-b8af-70106fb01274", "b661f158-8962-11e7-b8af-70106fb01274", "b661dcc3-8962-11e7-b8af-70106fb01274", "b661ab57-8962-11e7-b8af-70106fb01274", "b661a774-8962-11e7-b8af-70106fb01274", "b6612d3c-8962-11e7-b8af-70106fb01274", "b660c1f1-8962-11e7-b8af-70106fb01274", "b65f5d62-8962-11e7-b8af-70106fb01274", "b65dd56b-8962-11e7-b8af-70106fb01274"}; - List errIds = Arrays.asList(split); - for (String item : errIds) { - try { - Request request = new Request.Builder() - .url("http://120.55.168.18:8989/resumeZero/build/surplus/" + item) - .build(); - Response response = client - .newCall(request).execute(); - if (!response.isSuccessful()) { - - System.out.println(item + ": 服务器端错误: " + response); - continue; - } - -// Headers responseHeaders = response.headers(); -// for (int i = 0; i < responseHeaders.size(); i++) { -// System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); -// } - - System.out.println(response.body().string()); - System.out.println("================================================"); - } catch (Exception e) { - e.printStackTrace(); - } - - } - - } + public static void main(String[] args) throws IOException { + OkHttpClient client = new OkHttpClient(); + client.interceptors().add(new LoggingInterceptor()); + try { + Request request = new Request.Builder() + .url("http://localhost:54321/app/test") + .header("Authorization","Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwN2U5MTAyYzc5NTVlMTc3OTQ2NDcwZjcyNjY5MGZhNyIsImF1dGgiOlt7ImF1dGhvcml0eSI6IlJPTEVfQ0xJRU5UIn1dLCJpYXQiOjE1NTIxMjgzNjIsImV4cCI6MTU1MjEzMTk2Mn0.H4_KqWGgGc_YUSOezJ6c4fEmkiYp5NmXsQLbpWlgCv4") + .header("whoami","07e9102c7955e177946470f726690fa7") + .build(); + Response response = client.newCall(request).execute(); + if (!response.isSuccessful()) { + System.out.println("服务器端错误: " + response); + } + System.out.println(response.body().string()); + System.out.println("================================================"); + } catch (Exception e) { + e.printStackTrace(); + } + + + } } diff --git a/quick-okhttp/src/main/java/com/quick/test/PostExample.java b/quick-okhttp/src/main/java/com/quick/test/PostExample.java index 27833930..d7f1ae6e 100644 --- a/quick-okhttp/src/main/java/com/quick/test/PostExample.java +++ b/quick-okhttp/src/main/java/com/quick/test/PostExample.java @@ -61,8 +61,8 @@ String get(String url, HashMap paramsMap) throws IOException { public static void main(String[] args) throws IOException { -// ybSchool(); - company(); + ybSchool(); +// company(); } private static void company() throws IOException { @@ -77,7 +77,7 @@ private static void company() throws IOException { private static void ybSchool() throws IOException { ExecutorService executorService = Executors.newFixedThreadPool(20); - String path = "D:\\datafilter\\v0.2\\total_school.txt"; + String path = "D:\\datafilter\\v0.3\\total_school.txt"; List list = IOUtils.readLines(new FileInputStream(new File(path))); for (String item : list) { executorService.execute(new MyThread3(replaceBlank(item))); @@ -175,20 +175,25 @@ public void run() { jo.put("school", value); jsonArray.add(jo); long start = System.currentTimeMillis(); + System.out.println(jsonArray.toString()); response = example.post("http://192.168.1.31:9992/school/batch_normalize_v2", jsonArray.toString()); long end = System.currentTimeMillis(); processTime = (double)(end-start)/1000; JSONObject json = JSONObject.parseObject(response); if(json.containsKey("data")){ JSONObject data = json.getJSONArray("data").getJSONObject(0); - String enName = data.getString("en_name"); - String oldName = data.getString("old_name"); - String zhName = data.getString("zh_name"); - - if (StringUtils.isEmpty(enName) && StringUtils.isEmpty(zhName)) { + System.out.println(data.getBoolean("is_standard")); + if (data.getBoolean("is_standard")) { + String enName = data.getString("en_name"); + String oldName = data.getString("old_name"); + String zhName = data.getString("zh_name"); + if (!StringUtils.isEmpty(zhName)) { + name = zhName; + } + if (!StringUtils.isEmpty(enName)) { + name = enName; + } name = oldName; - }else { - name = StringUtils.isEmpty(zhName) ? enName : zhName; } } @@ -197,7 +202,7 @@ public void run() { } String result = "[" + value + "--->{" + name + "} 耗时: " + processTime + "]"; try { - File file = new File("D:\\datafilter\\v0.2\\total_school_result.txt"); + File file = new File("D:\\datafilter\\v0.3\\total_school_result.txt"); OutputStream os = new FileOutputStream(file, true); List lines = new ArrayList<>(); lines.add(result); diff --git a/quick-package-assembly-multi-env/pom.xml b/quick-package-assembly-multi-env/pom.xml index 42052f7e..2640ed40 100644 --- a/quick-package-assembly-multi-env/pom.xml +++ b/quick-package-assembly-multi-env/pom.xml @@ -10,15 +10,12 @@ jar - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml - - 5.1.30 - @@ -47,12 +44,10 @@ org.mybatis.spring.boot mybatis-spring-boot-starter - 1.1.1 mysql mysql-connector-java - ${mysql.version} diff --git a/quick-package-assembly-multi-env/src/main/resources/application.properties b/quick-package-assembly-multi-env/src/main/resources/application.properties index d616e0da..8facf5b7 100644 --- a/quick-package-assembly-multi-env/src/main/resources/application.properties +++ b/quick-package-assembly-multi-env/src/main/resources/application.properties @@ -3,7 +3,7 @@ server.port=9090 logging.level.root=INFO -spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/world?characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=root diff --git a/quick-package-assembly/pom.xml b/quick-package-assembly/pom.xml index f5bc87f1..6291c904 100644 --- a/quick-package-assembly/pom.xml +++ b/quick-package-assembly/pom.xml @@ -10,15 +10,12 @@ jar - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml - - 5.1.30 - @@ -47,12 +44,10 @@ org.mybatis.spring.boot mybatis-spring-boot-starter - 1.1.1 mysql mysql-connector-java - ${mysql.version} diff --git a/quick-package-assembly/src/main/resources/application.properties b/quick-package-assembly/src/main/resources/application.properties index d616e0da..8facf5b7 100644 --- a/quick-package-assembly/src/main/resources/application.properties +++ b/quick-package-assembly/src/main/resources/application.properties @@ -3,7 +3,7 @@ server.port=9090 logging.level.root=INFO -spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/world?characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=root diff --git a/quick-platform-common/pom.xml b/quick-platform-common/pom.xml new file mode 100644 index 00000000..a23832a4 --- /dev/null +++ b/quick-platform-common/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + quick-platform-common + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/quick-platform-common/src/main/java/com/quick/common/base/dto/BaseReqDto.java b/quick-platform-common/src/main/java/com/quick/common/base/dto/BaseReqDto.java new file mode 100644 index 00000000..d763e6fe --- /dev/null +++ b/quick-platform-common/src/main/java/com/quick/common/base/dto/BaseReqDto.java @@ -0,0 +1,6 @@ +package com.quick.common.base.dto; + +import java.io.Serializable; + +public class BaseReqDto implements Serializable { +} diff --git a/quick-platform-common/src/main/java/com/quick/common/base/dto/BaseRespDto.java b/quick-platform-common/src/main/java/com/quick/common/base/dto/BaseRespDto.java new file mode 100644 index 00000000..ec69344f --- /dev/null +++ b/quick-platform-common/src/main/java/com/quick/common/base/dto/BaseRespDto.java @@ -0,0 +1,6 @@ +package com.quick.common.base.dto; + +import java.io.Serializable; + +public class BaseRespDto implements Serializable { +} \ No newline at end of file diff --git a/quick-platform-common/src/main/java/com/quick/common/base/exception/BaseException.java b/quick-platform-common/src/main/java/com/quick/common/base/exception/BaseException.java new file mode 100644 index 00000000..269eb4a8 --- /dev/null +++ b/quick-platform-common/src/main/java/com/quick/common/base/exception/BaseException.java @@ -0,0 +1,31 @@ +package com.quick.common.base.exception; + +import com.quick.common.base.rest.SystemErrorType; + +public class BaseException extends RuntimeException { + /** + * 异常对应的错误类型 + */ + private final SystemErrorType errorType; + + /** + * 默认是系统异常 + */ + public BaseException() { + this.errorType = SystemErrorType.SYSTEM_ERROR; + } + + public BaseException(SystemErrorType errorType) { + this.errorType = errorType; + } + + public BaseException(SystemErrorType errorType, String message) { + super(message); + this.errorType = errorType; + } + + public BaseException(SystemErrorType errorType, String message, Throwable cause) { + super(message, cause); + this.errorType = errorType; + } +} \ No newline at end of file diff --git a/quick-platform-common/src/main/java/com/quick/common/base/exception/MessageException.java b/quick-platform-common/src/main/java/com/quick/common/base/exception/MessageException.java new file mode 100644 index 00000000..d1dba022 --- /dev/null +++ b/quick-platform-common/src/main/java/com/quick/common/base/exception/MessageException.java @@ -0,0 +1,14 @@ +package com.quick.common.base.exception; + +public class MessageException extends Exception { + + private static final long serialVersionUID = 1L; + + public MessageException(Exception exception) { + super(exception.getMessage(), exception.getCause()); + } + + public MessageException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/quick-platform-common/src/main/java/com/quick/common/base/rest/BaseController.java b/quick-platform-common/src/main/java/com/quick/common/base/rest/BaseController.java new file mode 100644 index 00000000..3f358389 --- /dev/null +++ b/quick-platform-common/src/main/java/com/quick/common/base/rest/BaseController.java @@ -0,0 +1,28 @@ +package com.quick.common.base.rest; + +/** + * 后续可在此提供公共方法 + */ +public interface BaseController { + + default BaseResp success() { + return BaseResp.success(); + } + + default BaseResp success(T data) { + return success(data,""); + } + + default BaseResp success(T data, String msg) { + return BaseResp.success(data, msg); + } + + default BaseResp fail(String msg) { + return BaseResp.fail(msg); + } + + default BaseResp fail(ResultStatus code, String msg) { + return BaseResp.fail(code, msg); + } + +} diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/BaseResp.java b/quick-platform-common/src/main/java/com/quick/common/base/rest/BaseResp.java similarity index 53% rename from quick-wx-api/src/main/java/com/quick/api/utils/BaseResp.java rename to quick-platform-common/src/main/java/com/quick/common/base/rest/BaseResp.java index 9db911e0..874cc9fa 100644 --- a/quick-wx-api/src/main/java/com/quick/api/utils/BaseResp.java +++ b/quick-platform-common/src/main/java/com/quick/common/base/rest/BaseResp.java @@ -1,4 +1,4 @@ -package com.quick.api.utils; +package com.quick.common.base.rest; import java.util.Date; @@ -9,7 +9,7 @@ public class BaseResp { /** * 返回码 */ - private int code; + private ResultStatus code; /** * 返回信息描述 @@ -23,11 +23,12 @@ public class BaseResp { private long currentTime; - public int getCode() { + + public ResultStatus getCode() { return code; } - public void setCode(int code) { + public void setCode(ResultStatus code) { this.code = code; } @@ -55,15 +56,15 @@ public void setCurrentTime(long currentTime) { this.currentTime = currentTime; } - public BaseResp(){} + public BaseResp() { + } /** - * - * @param code 错误码 + * @param code 错误码 * @param message 信息 - * @param data 数据 + * @param data 数据 */ - public BaseResp(int code, String message, T data) { + public BaseResp(ResultStatus code, String message, T data) { this.code = code; this.message = message; this.data = data; @@ -72,27 +73,56 @@ public BaseResp(int code, String message, T data) { /** * 不带数据的返回结果 + * * @param resultStatus */ public BaseResp(ResultStatus resultStatus) { - this.code = resultStatus.getErrorCode(); + this.code = resultStatus; this.message = resultStatus.getErrorMsg(); - this.data = data; this.currentTime = new Date().getTime(); } /** * 带数据的返回结果 + * * @param resultStatus * @param data */ public BaseResp(ResultStatus resultStatus, T data) { - this.code = resultStatus.getErrorCode(); + this.code = resultStatus; this.message = resultStatus.getErrorMsg(); this.data = data; this.currentTime = new Date().getTime(); } + public static BaseResp success() { + BaseResp response = new BaseResp(); + response.setCode(ResultStatus.SUCCESS); + return response; + } + + public static BaseResp success(T data, String msg) { + BaseResp response = new BaseResp(); + response.setCode(ResultStatus.SUCCESS); + response.setData(data); + response.setMessage(msg); + return response; + } + + public static BaseResp fail(String msg) { + BaseResp response = new BaseResp(); + response.setCode(ResultStatus.FAIL); + response.setMessage(msg); + return response; + } + + public static BaseResp fail(ResultStatus code, String msg) { + BaseResp response = new BaseResp(); + response.setCode(code); + response.setMessage(msg); + return response; + } + } diff --git a/quick-tika/src/main/java/com/quick/util/ResultStatus.java b/quick-platform-common/src/main/java/com/quick/common/base/rest/ResultStatus.java similarity index 85% rename from quick-tika/src/main/java/com/quick/util/ResultStatus.java rename to quick-platform-common/src/main/java/com/quick/common/base/rest/ResultStatus.java index 05b31a39..054cc129 100644 --- a/quick-tika/src/main/java/com/quick/util/ResultStatus.java +++ b/quick-platform-common/src/main/java/com/quick/common/base/rest/ResultStatus.java @@ -1,13 +1,11 @@ -package com.quick.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package com.quick.common.base.rest; /** 错误码 * @author vector * */ + public enum ResultStatus { // -1为通用失败(根据ApiResult.java中的构造方法注释而来) @@ -85,14 +83,7 @@ public enum ResultStatus { no_login(1000,"没有登录"), config_error(1001,"参数配置表错误"), user_exist(1002,"用户名已存在"), - userpwd_not_exist(1003,"用户名不存在或者密码错误"), - - - - - ; - private static final Logger LOGGER = LoggerFactory.getLogger(ResultStatus.class); - + userpwd_not_exist(1003,"用户名不存在或者密码错误"); private int code; private String msg; @@ -102,25 +93,6 @@ public enum ResultStatus { this.msg = msg; } - public static int getCode(String define){ - try { - return ResultStatus.valueOf(define).code; - } catch (IllegalArgumentException e) { - LOGGER.error("undefined error code: {}", define); - return FAIL.getErrorCode(); - } - } - - public static String getMsg(String define){ - try { - return ResultStatus.valueOf(define).msg; - } catch (IllegalArgumentException e) { - LOGGER.error("undefined error code: {}", define); - return FAIL.getErrorMsg(); - } - - } - public static String getMsg(int code){ for(ResultStatus err : ResultStatus.values()){ if(err.code==code){ @@ -138,4 +110,5 @@ public String getErrorMsg(){ return msg; } -} \ No newline at end of file +} + diff --git a/quick-platform-common/src/main/java/com/quick/common/base/rest/SystemErrorType.java b/quick-platform-common/src/main/java/com/quick/common/base/rest/SystemErrorType.java new file mode 100644 index 00000000..ba5d1159 --- /dev/null +++ b/quick-platform-common/src/main/java/com/quick/common/base/rest/SystemErrorType.java @@ -0,0 +1,54 @@ +package com.quick.common.base.rest; + + +public enum SystemErrorType { + + SYSTEM_ERROR("-1", "系统错误,请联系管理员"), + SYSTEM_BUSY("000001", "请求频繁,请稍候再试"), + + GATEWAY_NOT_FOUND_SERVICE("010404", "服务未找到"), + GATEWAY_ERROR("010500", "网关异常"), + GATEWAY_CONNECT_TIME_OUT("010002", "网关超时"), + GATEWAY_NOT_LOGIN("010003", "未登录"), + GATEWAY_NO_PERMISSION("010004", "无权限"), + USER_NOT_FOUND("010005", "用户不存在"), + PAASSWORD_ERROR("010006", "密码错误"), + + ARGUMENT_NOT_VALID("020000", "请求参数校验不通过"), + INVALID_TOKEN("020001", "无效token"), + /** 令牌无效 */ + TOKEN_INVALID("020002", "token failure!"), + /** 缺少相应参数 */ + MISSING_PARAMETER("020003", "参数绑定失败:缺少参数"), + /** 刷新令牌无效 */ + REFRESH_TOKEN_INVALID("020005", "认证过期,请重新登录"), + /** 验证码无效 */ + CODE_EXPIRE("020006", "secretKey无效"), + /** 用户名不存在 */ + USERNAME_NOT_EXIST("020008", "appKey不存在"), + + UPLOAD_FILE_SIZE_LIMIT("020010", "上传文件大小超过限制"), + DUPLICATE_PRIMARY_KEY("030000","唯一键冲突"); + + /** + * 错误类型码 + */ + private String code; + /** + * 错误类型描述信息 + */ + private String msg; + + SystemErrorType(String code, String msg) { + this.code = code; + this.msg = msg; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return msg; + } +} \ No newline at end of file diff --git a/quick-platform-component/.gitignore b/quick-platform-component/.gitignore new file mode 100644 index 00000000..5ff6309b --- /dev/null +++ b/quick-platform-component/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/quick-platform-component/pom.xml b/quick-platform-component/pom.xml new file mode 100644 index 00000000..b29afe34 --- /dev/null +++ b/quick-platform-component/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + com.quick.component + quick-platform-component + jar + + quick-platform-component + http://maven.apache.org + + + UTF-8 + + + + + + com.quick + quick-platform-common + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-test + + + + cn.hutool + hutool-all + + + + + + + org.projectlombok + lombok + + + diff --git a/quick-platform-component/src/main/java/com/quick/component/config/GlobalHandlerConfig.java b/quick-platform-component/src/main/java/com/quick/component/config/GlobalHandlerConfig.java new file mode 100644 index 00000000..8ea82a7b --- /dev/null +++ b/quick-platform-component/src/main/java/com/quick/component/config/GlobalHandlerConfig.java @@ -0,0 +1,15 @@ +package com.quick.component.config; + +import com.quick.common.base.rest.BaseResp; +import com.quick.common.base.rest.ResultStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalHandlerConfig { + + @ExceptionHandler(IllegalArgumentException.class) + public BaseResp exceptionHandler(Exception e) { + return new BaseResp<>(ResultStatus.error_invalid_argument); + } +} diff --git a/quick-platform-component/src/main/java/com/quick/component/config/logAspect/LogAdvice.java b/quick-platform-component/src/main/java/com/quick/component/config/logAspect/LogAdvice.java new file mode 100644 index 00000000..331b4ec3 --- /dev/null +++ b/quick-platform-component/src/main/java/com/quick/component/config/logAspect/LogAdvice.java @@ -0,0 +1,83 @@ +package com.quick.component.config.logAspect; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.util.StopWatch; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class LogAdvice implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + //记录请求信息 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + Object result = methodInvocation.proceed(); + stopWatch.stop(); + Method method = methodInvocation.getMethod(); + String urlStr = request.getRequestURL().toString(); + log.info("===================================begin=================================================="); + log.info("req path: {}", StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath())); + log.info("req ip: {}", request.getRemoteUser()); + log.info("req method: {}", request.getMethod()); + log.info("req params: {}", getParameter(method, methodInvocation.getArguments())); + log.info("req result: {}", JSONUtil.toJsonStr(result)); + log.info("req uri: {}", request.getRequestURI()); + log.info("req url: {}", request.getRequestURL().toString()); + log.info("req cost: {}ms", stopWatch.getLastTaskTimeMillis()); + log.info("===================================end==================================================="); + return result; + } + + private Object getParameter(Method method, Object[] args) { + List argList = new ArrayList<>(); + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + //将RequestBody注解修饰的参数作为请求参数 + RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); + if (requestBody != null) { + argList.add(args[i]); + } + //将RequestParam注解修饰的参数作为请求参数 + RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); + if (requestParam != null) { + Map map = new HashMap<>(); + String key = parameters[i].getName(); + if (!StringUtils.isEmpty(requestParam.value())) { + key = requestParam.value(); + } + map.put(key, args[i]); + argList.add(map); + } + } + if (argList.size() == 0) { + return null; + } else if (argList.size() == 1) { + return argList.get(0); + } else { + return argList; + } + } + + +} diff --git a/quick-platform-component/src/main/java/com/quick/component/config/logAspect/WebLogAspect.java b/quick-platform-component/src/main/java/com/quick/component/config/logAspect/WebLogAspect.java new file mode 100644 index 00000000..7570daf1 --- /dev/null +++ b/quick-platform-component/src/main/java/com/quick/component/config/logAspect/WebLogAspect.java @@ -0,0 +1,92 @@ +package com.quick.component.config.logAspect; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.util.StopWatch; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +//@Aspect +@Slf4j +public class WebLogAspect { + + @Around("execution(public * com..controller.*.*(..))") + public Object doAround(ProceedingJoinPoint pjp) throws Throwable { + //获取当前请求对象 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + //记录请求信息 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + Object result = pjp.proceed(); + stopWatch.stop(); + + Signature signature = pjp.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + String urlStr = request.getRequestURL().toString(); + log.info("===================================begin=================================================="); + log.info("req path: {}", StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath())); + log.info("req ip: {}", request.getRemoteUser()); + log.info("req method: {}", request.getMethod()); + log.info("req params: {}", getParameter(method, pjp.getArgs())); + log.info("req result: {}", JSONUtil.toJsonStr(result)); + log.info("req uri: {}", request.getRequestURI()); + log.info("req url: {}", request.getRequestURL().toString()); + log.info("req cost: {}ms", stopWatch.getLastTaskTimeMillis()); + log.info("===================================end==================================================="); + return result; + } + + + /** + * 根据方法和传入的参数获取请求参数 + */ + private Object getParameter(Method method, Object[] args) { + List argList = new ArrayList<>(); + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + //将RequestBody注解修饰的参数作为请求参数 + RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); + if (requestBody != null) { + argList.add(args[i]); + } + //将RequestParam注解修饰的参数作为请求参数 + RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); + if (requestParam != null) { + Map map = new HashMap<>(); + String key = parameters[i].getName(); + if (!StringUtils.isEmpty(requestParam.value())) { + key = requestParam.value(); + } + map.put(key, args[i]); + argList.add(map); + } + } + if (argList.size() == 0) { + return null; + } else if (argList.size() == 1) { + return argList.get(0); + } else { + return argList; + } + } +} diff --git a/quick-platform-component/src/main/java/com/quick/component/config/logAspect/WebLogAspectConfig.java b/quick-platform-component/src/main/java/com/quick/component/config/logAspect/WebLogAspectConfig.java new file mode 100644 index 00000000..7d259a85 --- /dev/null +++ b/quick-platform-component/src/main/java/com/quick/component/config/logAspect/WebLogAspectConfig.java @@ -0,0 +1,40 @@ +package com.quick.component.config.logAspect; + +import com.quick.component.enables.QsEnableAroundLog; +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; + +import java.util.function.Supplier; + +/** + * 动态设置切点表达式 + */ +@Slf4j +public class WebLogAspectConfig implements ImportBeanDefinitionRegistrar { + + + @Override + public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { + AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(QsEnableAroundLog.class.getName())); + String value = annoAttrs.getString("value"); + + log.info("init log aspect: {}", value); + BeanDefinitionBuilder beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AspectJExpressionPointcutAdvisor.class, new Supplier() { + @Override + public AspectJExpressionPointcutAdvisor get() { + AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); + advisor.setExpression(value); + advisor.setAdvice(new LogAdvice()); + return advisor; + } + }); + + registry.registerBeanDefinition("aspectJExpressionPointcutAdvisor", beanDefinition.getBeanDefinition()); + + } +} diff --git a/quick-platform-component/src/main/java/com/quick/component/enables/QsEnableAroundLog.java b/quick-platform-component/src/main/java/com/quick/component/enables/QsEnableAroundLog.java new file mode 100644 index 00000000..484aedba --- /dev/null +++ b/quick-platform-component/src/main/java/com/quick/component/enables/QsEnableAroundLog.java @@ -0,0 +1,18 @@ +package com.quick.component.enables; + +import com.quick.component.config.logAspect.WebLogAspectConfig; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * 接口请求日志 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(WebLogAspectConfig.class) +public @interface QsEnableAroundLog { + + String value() default "execution(public * com.*..controller.*.*(..))"; +} diff --git a/quick-platform-component/src/main/java/com/quick/component/enables/QsEnableGlobalExceptionHandler.java b/quick-platform-component/src/main/java/com/quick/component/enables/QsEnableGlobalExceptionHandler.java new file mode 100644 index 00000000..ecea43ae --- /dev/null +++ b/quick-platform-component/src/main/java/com/quick/component/enables/QsEnableGlobalExceptionHandler.java @@ -0,0 +1,13 @@ +package com.quick.component.enables; + +import com.quick.component.config.GlobalHandlerConfig; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(GlobalHandlerConfig.class) +public @interface QsEnableGlobalExceptionHandler { +} diff --git a/quick-platform-component/src/test/java/com/quick/component/AppTest.java b/quick-platform-component/src/test/java/com/quick/component/AppTest.java new file mode 100644 index 00000000..784ff2d8 --- /dev/null +++ b/quick-platform-component/src/test/java/com/quick/component/AppTest.java @@ -0,0 +1,38 @@ +package com.quick.component; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/quick-platform/README.md b/quick-platform/README.md new file mode 100644 index 00000000..990df91b --- /dev/null +++ b/quick-platform/README.md @@ -0,0 +1 @@ +## 不使用 spring-boot-starter-parent 构建 diff --git a/quick-platform/pom.xml b/quick-platform/pom.xml new file mode 100644 index 00000000..11813857 --- /dev/null +++ b/quick-platform/pom.xml @@ -0,0 +1,478 @@ + + + + + quick-platform + com.quick + 1.0-SNAPSHOT + 4.0.0 + pom + + ${project.artifactId} + + + + + UTF-8 + UTF-8 + 1.8 + + 8 + 8 + + 5.2.21.RELEASE + 2.3.4.RELEASE + + + 1.3.2 + 3.5.6 + 2.1.210 + 1.5.3 + 1.3.8.RELEASE + 2.7.11 + 3.0.3 + + 2.2.5.RELEASE + 8.0.28 + 1.1.10 + 1.2.83 + 2.7 + 1.4.19 + 230521-nf-execution + 2019-06-24T11-47-27-31ab4f9 + 31.0.1-jre + 5.7.22 + 4.1.1 + 3.4.2 + 3.5.1 + + + + 8.18.0 + 3.6.0 + 0.6.6 + 1.4.26 + 1.18.12 + 8.0.28 + + 4.5.13 + 3.6.0 + 2.1.3 + 3.4 + 2.2.1.RELEASE + 1.8 + 2.22.2 + 1.3.2 + 0.4.3 + 4.0 + 1.9.1 + 3.8.1 + + + 3.0 + 3.4 + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${springboot.version} + pom + import + + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + ${springcloud.alibaba.version} + pom + import + + + + + org.springframework.boot + spring-boot-starter-web + ${springboot.version} + + + org.apache.shiro + shiro-spring-boot-web-starter + ${shiro.springboot.version} + + + + org.springframework + spring-context + ${spring.version} + + + + org.springframework.boot + spring-boot-starter-data-redis + ${springboot.version} + + + + + + org.springframework.boot + spring-boot-starter-log4j + ${springboot.log4j.version} + + + + org.springframework.boot + spring-boot-starter-test + ${springboot.version} + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis.springboot.version} + + + + org.mybatis + mybatis + + ${org.mybatis.version} + + + com.h2database + h2 + ${h2.version} + + + + + org.apache.shardingsphere + sharding-jdbc-spring-boot-starter + ${sharding-boot-starter.version} + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus-generator.version} + + + + + com.alibaba.cloud + spring-cloud-starter-dubbo + ${springcloud.alibaba.version} + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + ${springcloud.alibaba.version} + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + ${springcloud.alibaba.version} + + + + + mysql + mysql-connector-java + ${mysql.version} + + + com.google.protobuf + protobuf-java + + + + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + ${jasypt.version} + + + + com.alibaba + druid-spring-boot-starter + ${druid.springboot.version} + + + + com.netflix.feign + feign-core + ${feign.version} + + + com.netflix.feign + feign-jackson + ${feign.version} + + + com.squareup.okhttp3 + mockwebserver + ${mockwebserver.version} + + + + + + com.netflix.feign + feign-okhttp + ${feign.version} + + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + + + + com.netflix.feign + feign-hystrix + ${feign.version} + + + + com.netflix.archaius + archaius-core + ${archaius-core.version} + + + + com.netflix.hystrix + hystrix-core + ${hystrix-core.version} + + + + com.netflix.feign + feign-slf4j + ${feign.version} + + + + + + cn.hutool + hutool-all + ${hutool-version} + + + com.graphql-java + graphql-java + ${graphql.version} + + + + + com.graphql-java + graphql-java-spring-boot-starter-webmvc + ${graphql.springboot.version} + + + + com.google.guava + guava + ${guava.version} + + + com.alibaba + fastjson + ${fastjson.version} + + + + commons-io + commons-io + ${commons-io.version} + + + + com.microsoft.sqlserver + sqljdbc4 + ${sqljdbc4.version} + + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + + org.apache.httpcomponents + httpmime + ${httpclient.version} + + + + org.projectlombok + lombok + ${lombok.version} + + + + org.apache.commons + commons-lang3 + ${commonslang3.version} + + + com.thoughtworks.xstream + xstream + ${xstream.version} + + + + + org.dom4j + dom4j + ${dom4j.version} + + + + + + + org.apache.shiro + shiro-core + ${shiro.version} + + + org.apache.shiro + shiro-cas + ${shiro.version} + + + org.apache.shiro + shiro-web + ${shiro.version} + + + org.apache.shiro + shiro-ehcache + ${shiro.version} + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + + + org.apache.shiro + shiro-quartz + ${shiro.version} + + + org.opensymphony.quartz + quartz + + + + + org.apache.shiro + shiro-spring + ${shiro.version} + + + org.apache.shiro + shiro-guice + ${shiro.version} + + + org.apache.shiro + shiro-aspectj + ${shiro.version} + + + org.apache.shiro + shiro-all + ${shiro.version} + + + + + org.apache.maven + maven-plugin-api + ${maven-plugin-api.version} + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven-plugin-annotations.version} + provided + + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-maven-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler.version} + + ${maven.compiler.source.version} + ${maven.compiler.target.version} + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.version} + + true + + + + org.mybatis.generator + mybatis-generator-maven-plugin + ${mybatis.generator.version} + + + + com.spotify + docker-maven-plugin + ${docker-maven.version} + + + + + + \ No newline at end of file diff --git a/quick-profiles/README.md b/quick-profiles/README.md new file mode 100644 index 00000000..0403d987 --- /dev/null +++ b/quick-profiles/README.md @@ -0,0 +1 @@ +# 使用profile控制需要引入的依赖、插件等等 \ No newline at end of file diff --git a/quick-profiles/api/pom.xml b/quick-profiles/api/pom.xml new file mode 100644 index 00000000..dad316b6 --- /dev/null +++ b/quick-profiles/api/pom.xml @@ -0,0 +1,72 @@ + + + + quick-profiles + com.quick + 1.0-SNAPSHOT + + 4.0.0 + + api + + + + + m1 + + + com.quick + module1 + 1.0-SNAPSHOT + + + + + + + + + m2 + + + com.quick + module2 + 1.0-SNAPSHOT + + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + com.profiles.TestMain + + true + lib/ + + + ./ + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file diff --git a/quick-profiles/api/src/main/java/com/profiles/TestMain.java b/quick-profiles/api/src/main/java/com/profiles/TestMain.java new file mode 100644 index 00000000..d7c1ead2 --- /dev/null +++ b/quick-profiles/api/src/main/java/com/profiles/TestMain.java @@ -0,0 +1,25 @@ +package com.profiles; + +import com.profiles.base.HelloService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import javax.annotation.PostConstruct; + +@SpringBootApplication +public class TestMain { + + @Autowired + private HelloService helloService; + + + @PostConstruct + public void init() { + helloService.sayHello(); + } + + public static void main(String[] args) { + SpringApplication.run(TestMain.class); + } +} diff --git a/quick-profiles/api/src/main/resources/application.yml b/quick-profiles/api/src/main/resources/application.yml new file mode 100644 index 00000000..911f6067 --- /dev/null +++ b/quick-profiles/api/src/main/resources/application.yml @@ -0,0 +1,3 @@ +#spring: +# profiles: +# active: m2 \ No newline at end of file diff --git a/quick-profiles/base/pom.xml b/quick-profiles/base/pom.xml new file mode 100644 index 00000000..f61c3f52 --- /dev/null +++ b/quick-profiles/base/pom.xml @@ -0,0 +1,17 @@ + + + + quick-profiles + com.quick + 1.0-SNAPSHOT + + + + 4.0.0 + + base + + + \ No newline at end of file diff --git a/quick-profiles/base/src/main/java/com/profiles/base/HelloService.java b/quick-profiles/base/src/main/java/com/profiles/base/HelloService.java new file mode 100644 index 00000000..6c441a8e --- /dev/null +++ b/quick-profiles/base/src/main/java/com/profiles/base/HelloService.java @@ -0,0 +1,6 @@ +package com.profiles.base; + +public interface HelloService { + + void sayHello(); +} diff --git a/quick-profiles/module1/pom.xml b/quick-profiles/module1/pom.xml new file mode 100644 index 00000000..43749409 --- /dev/null +++ b/quick-profiles/module1/pom.xml @@ -0,0 +1,26 @@ + + + + quick-profiles + com.quick + 1.0-SNAPSHOT + + 4.0.0 + + module1 + + + + + base + com.quick + 1.0-SNAPSHOT + + + + + + + \ No newline at end of file diff --git a/quick-profiles/module1/src/main/java/com/profiles/module1/HelloServiceImpl.java b/quick-profiles/module1/src/main/java/com/profiles/module1/HelloServiceImpl.java new file mode 100644 index 00000000..82f9839b --- /dev/null +++ b/quick-profiles/module1/src/main/java/com/profiles/module1/HelloServiceImpl.java @@ -0,0 +1,11 @@ +package com.profiles.module1; + +import com.profiles.base.HelloService; +import org.springframework.stereotype.Service; + +@Service +public class HelloServiceImpl implements HelloService { + public void sayHello() { + System.out.println("say module1 hello"); + } +} diff --git a/quick-profiles/module2/pom.xml b/quick-profiles/module2/pom.xml new file mode 100644 index 00000000..2f9264b1 --- /dev/null +++ b/quick-profiles/module2/pom.xml @@ -0,0 +1,23 @@ + + + + quick-profiles + com.quick + 1.0-SNAPSHOT + + 4.0.0 + + module2 + + + + + base + com.quick + 1.0-SNAPSHOT + + + + \ No newline at end of file diff --git a/quick-profiles/module2/src/main/java/com/profiles/module2/HelloServiceImpl.java b/quick-profiles/module2/src/main/java/com/profiles/module2/HelloServiceImpl.java new file mode 100644 index 00000000..b0b87348 --- /dev/null +++ b/quick-profiles/module2/src/main/java/com/profiles/module2/HelloServiceImpl.java @@ -0,0 +1,12 @@ +package com.profiles.module2; + +import com.profiles.base.HelloService; +import org.springframework.stereotype.Service; + +@Service +public class HelloServiceImpl implements HelloService { + + public void sayHello() { + System.out.println("say module2 hello"); + } +} diff --git a/quick-profiles/pom.xml b/quick-profiles/pom.xml new file mode 100644 index 00000000..3cfe6378 --- /dev/null +++ b/quick-profiles/pom.xml @@ -0,0 +1,26 @@ + + + quick-profiles + 1.0-SNAPSHOT + 4.0.0 + pom + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + api + module1 + module2 + base + + + + \ No newline at end of file diff --git a/quick-rabbitmq/README.md b/quick-rabbitmq/README.md new file mode 100644 index 00000000..2b9f2624 --- /dev/null +++ b/quick-rabbitmq/README.md @@ -0,0 +1,177 @@ + +### 开头 + +先熟悉下面会用到的一些名词~ + +- exchange: 交换机 + +- routingkey: 路由key + +- queue: 队列 + +**exchange和queue是需要绑定在一起的,然后消息发送到exchange再由exchange通过routingkey发送到对应的队列中。** + +[![mq.png](http://upload-images.jianshu.io/upload_images/3167229-6c4b5b9265af981d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://i.loli.net/2018/06/14/5b21e04409004.png) + +(不是这张图~~~) + +exchange分四种 + +#### Default Exchange +这种是特殊的Direct Exchange,是rabbitmq内部默认的一个交换机。该交换机的name是空字符串,所有queue都默认binding 到该交换机上。所有binding到该交换机上的queue,routing-key都和queue的name一样。 + +**注意: 这就是为什么你直接创建一个queue也能正常的生产与消费,因为对应的exchange是RabbitMQ默认的,routingkey就是该队列的名字** + +#### Topic Exchange +通配符交换机,exchange会把消息发送到一个或者多个满足通配符规则的routing-key的queue。其中*表号匹配一个word,#匹配多个word和路径,路径之间通过.隔开。如满足a.*.c的routing-key有a.hello.c;满足#.hello的routing-key有a.b.c.helo。 + +#### Fanout Exchange +扇形交换机,该交换机会把消息发送到所有binding到该交换机上的queue。这种是publisher/subcribe模式。用来做广播最好。 +所有该exchagne上指定的routing-key都会被ignore掉。 + +#### Header Exchange +设置header attribute参数类型的交换机。 + +简单的了解之后,下面就是延迟队列的实现方式 + +### 延迟队列的实现 + +延迟分两种 + + - 在msg上设置过期时间 + - 在队列上设置过期时间 + +*一定要看懂这张图!!!* +[![WX20180613-233153@2x.png](http://upload-images.jianshu.io/upload_images/3167229-47316d92517586ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://i.loli.net/2018/06/13/5b213948c6e1a.png) + + +如上图创建三个exchange和三个队列 + +```java +@Bean +public DirectExchange delayExchange() { + return new DirectExchange(DELAY_EXCHANGE_NAME); +} + +@Bean +public DirectExchange processExchange() { + return new DirectExchange(PROCESS_EXCHANGE_NAME); +} + +@Bean +public DirectExchange delayQueueExchange() { + return new DirectExchange(DELAY_QUEUE_EXCHANGE_NAME); +} + +/** + * 存放延迟消息的队列 最后将会转发给exchange(实际消费队列对应的) + * @return + */ +@Bean +Queue delayQueue4Msg(){ + return QueueBuilder.durable(DELAY_QUEUE_MSG) + .withArgument("x-dead-letter-exchange", PROCESS_EXCHANGE_NAME) + .withArgument("x-dead-letter-routing-key", ROUTING_KEY) + .build(); +} + +@Bean +public Queue processQueue() { + return QueueBuilder.durable(PROCESS_QUEUE) + .build(); +} + +/** + * 存放消息的延迟队列 最后将会转发给exchange(实际消费队列对应的) + * @return + */ +@Bean +public Queue delayQueue4Queue() { + return QueueBuilder.durable(DELAY_QUEUE_NAME) + .withArgument("x-dead-letter-exchange", PROCESS_EXCHANGE_NAME) // DLX + .withArgument("x-dead-letter-routing-key", ROUTING_KEY) + .withArgument("x-message-ttl", 3000) // 设置队列的过期时间 单位毫秒 + .build(); +} +``` + +接下来将每个exchange和对应的mq绑定 +```java +@Bean +Binding delayBinding() { + return BindingBuilder.bind(delayQueue4Msg()) + .to(delayExchange()) + .with(ROUTING_KEY); +} + +@Bean +Binding queueBinding() { + return BindingBuilder.bind(processQueue()) + .to(processExchange()) + .with(ROUTING_KEY); +} +@Bean +Binding delayQueueBind() { + return BindingBuilder.bind(delayQueue4Queue()) + .to(delayQueueExchange()) + .with(ROUTING_KEY); +} +``` + +发送消息的方式 +```java +public void sendDelayMsg(Msg msg) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + System.out.println(msg.getId() + " 延迟消息发送时间:" + sdf.format(new Date())); + rabbitTemplate.convertAndSend(RabbitConfig.DELAY_EXCHANGE_NAME, "delay", msg, new MessagePostProcessor() { + @Override + public Message postProcessMessage(Message message) throws AmqpException { + message.getMessageProperties().setExpiration(msg.getTtl() + ""); + return message; + } + }); +} + +public void sendDelayQueue(Msg msg) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + System.out.println(msg.getId() + " 延迟队列消息发送时间:" + sdf.format(new Date())); + rabbitTemplate.convertAndSend(RabbitConfig.DELAY_QUEUE_EXCHANGE_NAME,"delay", msg); +} +``` + +### 验证结果 + +为每个消息设置过期时间 +[![延迟消息.gif](http://upload-images.jianshu.io/upload_images/3167229-dbb16df8756346d6.gif?imageMogr2/auto-orient/strip)](https://i.loli.net/2018/06/14/5b2206367916d.gif) + + +为队列设置过期时间 +[![延迟队列消息.gif](http://upload-images.jianshu.io/upload_images/3167229-f5fc967fdada17b9.gif?imageMogr2/auto-orient/strip)](https://i.loli.net/2018/06/14/5b2206367110f.gif) + + +*如果你把设置了过期时间的消息发送到设置了过期时间的队里中的时候,以最短的时间为准~~* + + + + + +### 最后 + +其实我在实现的过程中也花了很长的时间,主要就是被exchange和queue搞乱掉了,最后索性自己画了个图,按照图来一个一个创建与绑定。之后就很清晰很容易的实现了。 + +>强调!!! 如果在开发的过程中发现exchange和queue绑定错误了,建议从管理界面将queue和exchange unbind或者删除重新创建! + +代码已上传到Github上[Here](https://github.com/vector4wang/spring-boot-quick/tree/master/quick-rabbitmq) + + +CSDN:http://blog.csdn.net/qqhjqs?viewmode=list + +博客:http://blog.wangxc.club + +简书:https://www.jianshu.com/u/223a1314e818 + +Github:https://github.com/vector4wang + +Gitee:https://gitee.com/backwxc + +如果感觉有帮助的话,点个赞哦~ diff --git a/quick-rabbitmq/pom.xml b/quick-rabbitmq/pom.xml index a59e5f0d..00b3d621 100644 --- a/quick-rabbitmq/pom.xml +++ b/quick-rabbitmq/pom.xml @@ -16,13 +16,15 @@ org.springframework.boot spring-boot-starter-parent - 1.3.7.RELEASE + 1.5.7.RELEASE 2.2.2 + 1.8 + org.springframework.boot diff --git a/quick-rabbitmq/src/main/java/com/quick/mq/HelloApplication.java b/quick-rabbitmq/src/main/java/com/quick/mq/HelloApplication.java index 5c69fba1..31436a65 100644 --- a/quick-rabbitmq/src/main/java/com/quick/mq/HelloApplication.java +++ b/quick-rabbitmq/src/main/java/com/quick/mq/HelloApplication.java @@ -23,17 +23,17 @@ public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() - .apis(RequestHandlerSelectors.basePackage("com.quick.mq.controller")) //扫描API的包路径 + .apis(RequestHandlerSelectors.basePackage("com.quick.mq.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() - .title("SpringBootRabbitMQ") // 标题 - .description("api接口的文档整理,支持在线测试") // 描述 - .termsOfServiceUrl("http://vector4wang.tk/") //网址 - .contact("Vector.Wang") // 作者 - .version("1.0") // 版本号 + .title("SpringBootRabbitMQ") + .description("api接口的文档整理,支持在线测试") + .termsOfServiceUrl("http://blog.wangxc.club/") + .contact("Vector.Wang") + .version("1.0") .build(); } diff --git a/quick-rabbitmq/src/main/java/com/quick/mq/config/RabbitConfig.java b/quick-rabbitmq/src/main/java/com/quick/mq/config/RabbitConfig.java index 666f1340..0e35496a 100644 --- a/quick-rabbitmq/src/main/java/com/quick/mq/config/RabbitConfig.java +++ b/quick-rabbitmq/src/main/java/com/quick/mq/config/RabbitConfig.java @@ -4,6 +4,7 @@ import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,109 +12,252 @@ @Configuration public class RabbitConfig { - @Bean - public Queue helloQueue() { - return new Queue("helloQueue"); - } - - @Bean - public Queue msgQueue() { - return new Queue("msgQueue"); - } - //===============以下是验证topic Exchange的队列========== - @Bean - public Queue queueMessage() { - return new Queue("topic.message"); - } - - @Bean - public Queue queueMessages() { - return new Queue("topic.messages"); - } - //===============以上是验证topic Exchange的队列========== - - - //===============以下是验证Fanout Exchange的队列========== - @Bean - public Queue AMessage() { - return new Queue("fanout.A"); - } - - @Bean - public Queue BMessage() { - return new Queue("fanout.B"); - } - - @Bean - public Queue CMessage() { - return new Queue("fanout.C"); - } - //===============以上是验证Fanout Exchange的队列========== - - - @Bean - TopicExchange exchange() { - return new TopicExchange("exchange"); - } - @Bean - FanoutExchange fanoutExchange() { - return new FanoutExchange("fanoutExchange"); - } - - /** - * 将队列topic.message与exchange绑定,binding_key为topic.message,就是完全匹配 - * @param queueMessage - * @param exchange - * @return - */ - @Bean - Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) { - return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message"); - } - - /** - * 将队列topic.messages与exchange绑定,binding_key为topic.#,模糊匹配 - * @param queueMessages - * @param exchange - * @return - */ - @Bean - Binding bindingExchangeMessages(Queue queueMessages, TopicExchange exchange) { - return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#"); - } - - @Bean - Binding bindingExchangeA(Queue AMessage,FanoutExchange fanoutExchange) { - return BindingBuilder.bind(AMessage).to(fanoutExchange); - } - - @Bean - Binding bindingExchangeB(Queue BMessage, FanoutExchange fanoutExchange) { - return BindingBuilder.bind(BMessage).to(fanoutExchange); - } - - @Bean - Binding bindingExchangeC(Queue CMessage, FanoutExchange fanoutExchange) { - return BindingBuilder.bind(CMessage).to(fanoutExchange); - } - - @Bean - public ConnectionFactory connectionFactory() { - CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); - connectionFactory.setAddresses("60.205.191.82:5672"); - connectionFactory.setUsername("guest"); - connectionFactory.setPassword("guest"); - connectionFactory.setVirtualHost("/"); - connectionFactory.setPublisherConfirms(true); //必须要设置 - return connectionFactory; - } - - @Bean - @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - //必须是prototype类型 - public RabbitTemplate rabbitTemplate() { - RabbitTemplate template = new RabbitTemplate(connectionFactory()); - return template; - } + + + @Value("${rabbitmq.host}") + private String host; + + @Value("${rabbitmq.port}") + private int port; + + @Value("${rabbitmq.username}") + private String username; + + @Value("${rabbitmq.password}") + private String password; + + @Value("${rabbitmq.publisher-confirms}") + private boolean publisherConfirm; + + @Value("${rabbitmq.virtual-host}") + private String virtualHost; + + + + @Bean + public ConnectionFactory connectionFactory() { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + connectionFactory.setHost(host); + connectionFactory.setPort(port); + connectionFactory.setUsername(username); + connectionFactory.setPassword(password); + connectionFactory.setVirtualHost(virtualHost); + connectionFactory.setPublisherConfirms(publisherConfirm); //必须要设置 + return connectionFactory; + } + + //////////////////////////////针对消息 delay queue//////////////////////////////// + /** + * 发送到该队列的message会在一段时间后过期进入到delay_process_queue + * 每个message可以控制自己的失效时间 + */ + public final static String DELAY_QUEUE_MSG = "delay_queue"; + + /** + * DLX + */ + public final static String DELAY_EXCHANGE_NAME = "delay_exchange"; + + + /** + * 正常消费的队列 + */ + public final static String PROCESS_QUEUE = "process_queue"; + + /** + * 正常队列对应的exchange + */ + public final static String PROCESS_EXCHANGE_NAME = "process_exchange"; + + + public static String ROUTING_KEY = "delay"; + + /** + * 延迟队列 exchange + * @return + */ + @Bean + public DirectExchange delayExchange() { + return new DirectExchange(DELAY_EXCHANGE_NAME); + } + + @Bean + public DirectExchange processExchange() { + return new DirectExchange(PROCESS_EXCHANGE_NAME); + } + + /** + * 存放延迟消息的队列 最后将会转发给exchange(实际消费队列对应的) + * @return + */ + @Bean + Queue delayQueue4Msg(){ + return QueueBuilder.durable(DELAY_QUEUE_MSG) + .withArgument("x-dead-letter-exchange", PROCESS_EXCHANGE_NAME) // DLX,dead letter发送到的exchange + .withArgument("x-dead-letter-routing-key", ROUTING_KEY) // dead letter携带的routing key + .build(); + } + + @Bean + public Queue processQueue() { + return QueueBuilder.durable(PROCESS_QUEUE) + .build(); + } + + + + /** + * 将延迟队列与exchange绑定,即到达指定时间之后需要转交给queue消费 + * @return + */ + @Bean + Binding delayBinding() { + return BindingBuilder.bind(delayQueue4Msg()) + .to(delayExchange()) + .with(ROUTING_KEY); + } + + @Bean + Binding queueBinding() { + return BindingBuilder.bind(processQueue()) + .to(processExchange()) + .with(ROUTING_KEY); + } + + //////////////////////////////delay//////////////////////////////// + + + //////////////////////////////针对队列delay//////////////////////////////// + + /** + * 针对队列设置过期时间的队列 + */ + public final static String DELAY_QUEUE_NAME = "delay_queue_queue"; + + public final static String DELAY_QUEUE_EXCHANGE_NAME = "delay_queue_exchange"; + + @Bean + public DirectExchange delayQueueExchange() { + return new DirectExchange(DELAY_QUEUE_EXCHANGE_NAME); + } + + /** + * 存放消息的延迟队列 最后将会转发给exchange(实际消费队列对应的) + * @return + */ + @Bean + public Queue delayQueue4Queue() { + return QueueBuilder.durable(DELAY_QUEUE_NAME) + .withArgument("x-dead-letter-exchange", PROCESS_EXCHANGE_NAME) // DLX + .withArgument("x-dead-letter-routing-key", ROUTING_KEY) // dead letter携带的routing key + .withArgument("x-message-ttl", 3000) // 设置队列的过期时间 + .build(); + } + + @Bean + Binding delayQueueBind() { + return BindingBuilder.bind(delayQueue4Queue()) + .to(delayQueueExchange()) + .with(ROUTING_KEY); + } + + + + @Bean + public Queue helloQueue() { + return new Queue("helloQueue"); + } + + @Bean + public Queue msgQueue() { + return new Queue("msgQueue"); + } + + //===============以下是验证topic Exchange的队列========== + @Bean + public Queue queueMessage() { + return new Queue("topic.message"); + } + + @Bean + public Queue queueMessages() { + return new Queue("topic.messages"); + } + //===============以上是验证topic Exchange的队列========== + + + //===============以下是验证Fanout Exchange的队列========== + @Bean + public Queue AMessage() { + return new Queue("fanout.A"); + } + + @Bean + public Queue BMessage() { + return new Queue("fanout.B"); + } + + @Bean + public Queue CMessage() { + return new Queue("fanout.C"); + } + //===============以上是验证Fanout Exchange的队列========== + + + @Bean + TopicExchange exchange() { + return new TopicExchange("exchange"); + } + + @Bean + FanoutExchange fanoutExchange() { + return new FanoutExchange("fanoutExchange"); + } + + /** + * 将队列topic.message与exchange绑定,binding_key为topic.message,就是完全匹配 + * @param queueMessage + * @param exchange + * @return + */ + @Bean + Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) { + return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message"); + } + + /** + * 将队列topic.messages与exchange绑定,binding_key为topic.#,模糊匹配 + * @param queueMessages + * @param exchange + * @return + */ + @Bean + Binding bindingExchangeMessages(Queue queueMessages, TopicExchange exchange) { + return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#"); + } + + @Bean + Binding bindingExchangeA(Queue AMessage, FanoutExchange fanoutExchange) { + return BindingBuilder.bind(AMessage).to(fanoutExchange); + } + + @Bean + Binding bindingExchangeB(Queue BMessage, FanoutExchange fanoutExchange) { + return BindingBuilder.bind(BMessage).to(fanoutExchange); + } + + @Bean + Binding bindingExchangeC(Queue CMessage, FanoutExchange fanoutExchange) { + return BindingBuilder.bind(CMessage).to(fanoutExchange); + } + + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + //必须是prototype类型 + public RabbitTemplate rabbitTemplate() { + RabbitTemplate template = new RabbitTemplate(connectionFactory()); + return template; + } } \ No newline at end of file diff --git a/quick-rabbitmq/src/main/java/com/quick/mq/controller/DelayController.java b/quick-rabbitmq/src/main/java/com/quick/mq/controller/DelayController.java new file mode 100644 index 00000000..f515a7d7 --- /dev/null +++ b/quick-rabbitmq/src/main/java/com/quick/mq/controller/DelayController.java @@ -0,0 +1,47 @@ +package com.quick.mq.controller; + + +import com.quick.mq.model.Msg; +import com.quick.mq.scenes.delayTask.DelaySender; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * http://blog.battcn.com/2018/05/23/springboot/v2-queue-rabbitmq-delay/ + */ + +@RestController +@RequestMapping("/delay") +@Api("延迟队列") +public class DelayController { + + @Autowired + private DelaySender delaySender; + + + @ApiOperation("延时队列发送(发送消息的时候设置过期时间)") + @RequestMapping(value = "/sendDelay", method = RequestMethod.POST) + public String sendDelayMsg(@RequestBody Msg msg) { + + delaySender.sendDelayMsg(msg); + + return "success"; + + } + + @ApiOperation("延时队列发送(整个队列设置过期时间,与msg没有关系)") + @RequestMapping(value = "/sendQueueDelay", method = RequestMethod.POST) + public String sendDelayQueueMsg(@RequestBody Msg msg) { + + delaySender.sendDelayQueue(msg); + + return "success"; + + } + +} diff --git a/quick-rabbitmq/src/main/java/com/quick/mq/model/Msg.java b/quick-rabbitmq/src/main/java/com/quick/mq/model/Msg.java index 81aba4a5..671e072a 100644 --- a/quick-rabbitmq/src/main/java/com/quick/mq/model/Msg.java +++ b/quick-rabbitmq/src/main/java/com/quick/mq/model/Msg.java @@ -12,6 +12,7 @@ public class Msg implements Serializable { private int id; private String content; + private long ttl; public int getId() { return id; @@ -29,5 +30,16 @@ public void setContent(String content) { this.content = content; } + public long getTtl() { + return ttl; + } + public void setTtl(long ttl) { + this.ttl = ttl; + } + + @Override + public String toString() { + return "Msg{" + "id=" + id + ", content='" + content + '\'' + ", ttl=" + ttl + '}'; + } } diff --git a/quick-rabbitmq/src/main/java/com/quick/mq/scenes/delayTask/DelayReceiver.java b/quick-rabbitmq/src/main/java/com/quick/mq/scenes/delayTask/DelayReceiver.java new file mode 100644 index 00000000..3dfbc5b6 --- /dev/null +++ b/quick-rabbitmq/src/main/java/com/quick/mq/scenes/delayTask/DelayReceiver.java @@ -0,0 +1,19 @@ +package com.quick.mq.scenes.delayTask; + +import com.quick.mq.config.RabbitConfig; +import com.quick.mq.model.Msg; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +import java.text.SimpleDateFormat; +import java.util.Date; + +@Component +public class DelayReceiver { + + @RabbitListener(queues = {RabbitConfig.PROCESS_QUEUE}) + public void process(Msg msg) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + System.out.println("当前时间: " + sdf.format(new Date()) + " ---> msg:【" + msg + "]"); + } +} diff --git a/quick-rabbitmq/src/main/java/com/quick/mq/scenes/delayTask/DelaySender.java b/quick-rabbitmq/src/main/java/com/quick/mq/scenes/delayTask/DelaySender.java index db35d747..bb1a53c3 100644 --- a/quick-rabbitmq/src/main/java/com/quick/mq/scenes/delayTask/DelaySender.java +++ b/quick-rabbitmq/src/main/java/com/quick/mq/scenes/delayTask/DelaySender.java @@ -1,5 +1,17 @@ package com.quick.mq.scenes.delayTask; +import com.quick.mq.config.RabbitConfig; +import com.quick.mq.model.Msg; +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessagePostProcessor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.text.SimpleDateFormat; +import java.util.Date; + /** * @Author: wangxc * @GitHub: https://github.com/vector4wang @@ -7,5 +19,35 @@ * @BLOG: http://vector4wang.tk * @wxid: BMHJQS */ +@Component public class DelaySender { + @Autowired + private AmqpTemplate rabbitTemplate; + + + /** + * 在消息上设置时间 + * @param msg + */ + public void sendDelayMsg(Msg msg) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + System.out.println(msg.getId() + " 延迟消息发送时间:" + sdf.format(new Date())); + rabbitTemplate.convertAndSend(RabbitConfig.DELAY_EXCHANGE_NAME, "delay", msg, new MessagePostProcessor() { + @Override + public Message postProcessMessage(Message message) throws AmqpException { + message.getMessageProperties().setExpiration(msg.getTtl() + ""); + return message; + } + }); + } + + /** + * 在队列上设置时间,则消息不需要任何处理 + * @param msg + */ + public void sendDelayQueue(Msg msg) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + System.out.println(msg.getId() + " 延迟队列消息发送时间:" + sdf.format(new Date())); + rabbitTemplate.convertAndSend(RabbitConfig.DELAY_QUEUE_EXCHANGE_NAME,"delay", msg); + } } diff --git a/quick-rabbitmq/src/main/java/com/quick/mq/util/ExpirationMessagePostProcessor.java b/quick-rabbitmq/src/main/java/com/quick/mq/util/ExpirationMessagePostProcessor.java new file mode 100644 index 00000000..f438791d --- /dev/null +++ b/quick-rabbitmq/src/main/java/com/quick/mq/util/ExpirationMessagePostProcessor.java @@ -0,0 +1,21 @@ +package com.quick.mq.util; + +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessagePostProcessor; + +public class ExpirationMessagePostProcessor implements MessagePostProcessor { + private final Long ttl; // 毫秒 + + public ExpirationMessagePostProcessor(Long ttl) { + this.ttl = ttl; + } + + @Override + public Message postProcessMessage(Message message) throws AmqpException { + message.getMessageProperties() + .setExpiration(ttl.toString()); // 设置per-message的失效时间 + return message; + } + +} \ No newline at end of file diff --git a/quick-rabbitmq/src/main/resources/application.properties b/quick-rabbitmq/src/main/resources/application.properties index 399e00b7..7bf5705c 100644 --- a/quick-rabbitmq/src/main/resources/application.properties +++ b/quick-rabbitmq/src/main/resources/application.properties @@ -1,10 +1,13 @@ server.port=8080 spring.application.name=spring-boot-rabbitmq -spring.rabbitmq.host=60.205.191.82 -spring.rabbitmq.port=5672 -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest -spring.rabbitmq.publisher-confirms=true -spring.rabbitmq.virtual-host=/ + + +rabbitmq.host=60.205.191.82 +rabbitmq.port=5673 +rabbitmq.username=guest +rabbitmq.password=guest +rabbitmq.publisher-confirms=true +rabbitmq.virtual-host=/ + diff --git a/quick-redies/pom.xml b/quick-redies/pom.xml index 76be01c0..49ba615e 100644 --- a/quick-redies/pom.xml +++ b/quick-redies/pom.xml @@ -3,15 +3,14 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> quick-redies - com.quick 1.0-SNAPSHOT 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml @@ -32,7 +31,7 @@ org.springframework.boot - spring-boot-starter-redis + spring-boot-starter-data-redis @@ -40,7 +39,6 @@ com.alibaba fastjson - 1.2.7 diff --git a/quick-redies/src/main/java/com/quick/redis/config/RedisConfig.java b/quick-redies/src/main/java/com/quick/redis/config/RedisConfig.java index 7155ab84..09b7965d 100644 --- a/quick-redies/src/main/java/com/quick/redis/config/RedisConfig.java +++ b/quick-redies/src/main/java/com/quick/redis/config/RedisConfig.java @@ -22,6 +22,7 @@ public class RedisConfig extends CachingConfigurerSupport { @Bean + @Override public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override diff --git a/quick-rest-template/pom.xml b/quick-rest-template/pom.xml new file mode 100644 index 00000000..0c83f1ab --- /dev/null +++ b/quick-rest-template/pom.xml @@ -0,0 +1,43 @@ + + + quick-rest-template + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + org.projectlombok + lombok + + + org.springframework + spring-web + 5.0.6.RELEASE + compile + + + + com.alibaba + fastjson + + + + + \ No newline at end of file diff --git a/quick-rest-template/src/main/java/com/rest/template/Application.java b/quick-rest-template/src/main/java/com/rest/template/Application.java new file mode 100644 index 00000000..e759a5d0 --- /dev/null +++ b/quick-rest-template/src/main/java/com/rest/template/Application.java @@ -0,0 +1,16 @@ +package com.rest.template; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author vector + * @date: 2019/3/15 0015 9:19 + */ +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class); + } +} + diff --git a/quick-rest-template/src/main/java/com/rest/template/config/RestTemplateConfig.java b/quick-rest-template/src/main/java/com/rest/template/config/RestTemplateConfig.java new file mode 100644 index 00000000..f6cc8b34 --- /dev/null +++ b/quick-rest-template/src/main/java/com/rest/template/config/RestTemplateConfig.java @@ -0,0 +1,28 @@ +package com.rest.template.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * @author vector + * @date: 2019/3/15 0015 9:15 + */ +@Configuration +public class RestTemplateConfig { + @Bean + public RestTemplate restTemplate(ClientHttpRequestFactory factory) { + return new RestTemplate(factory); + } + + @Bean + public ClientHttpRequestFactory simpleClientHttpRequestFactory() { + // 可以自定义factory + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setReadTimeout(5000);//ms + factory.setConnectTimeout(15000);//ms + return factory; + } +} diff --git a/quick-rest-template/src/main/java/com/rest/template/controller/TestController.java b/quick-rest-template/src/main/java/com/rest/template/controller/TestController.java new file mode 100644 index 00000000..bcb507c6 --- /dev/null +++ b/quick-rest-template/src/main/java/com/rest/template/controller/TestController.java @@ -0,0 +1,79 @@ +package com.rest.template.controller; + +import com.rest.template.model.TestDTO; +import org.springframework.web.bind.annotation.*; + +/** + * 测试接口 + */ +@RestController +public class TestController { + + /** + * get方法测试 + * + * @return + */ + @GetMapping("testGet") + public TestDTO testGet() { + TestDTO TestDTO = new TestDTO(); + TestDTO.setId(1); + TestDTO.setName("get"); + return TestDTO; + } + + /** + * post方法 + * + * @return + */ + @PostMapping("testPost") + public TestDTO testPost(@RequestBody TestDTO testDTO) { + testDTO.setName("from server " + testDTO.getName()); + return testDTO; + } + + + /** + * post 方法传值 + * + * @param id + * @param name + * @return + */ + @PostMapping("testPostParam") + public String testPostParam(@RequestParam("id") String id, @RequestParam("name") String name) { + System.out.println("Post id:" + id); + System.out.println("Post name:" + name); + return "post succ"; + } + + + /** + * post 方法传值 + * + * @param id + * @param name + * @return + */ + @PutMapping("testPut") + public String testPut(@RequestParam("id") String id, @RequestParam("name") String name) { + System.out.println("put id:" + id); + System.out.println("put name:" + name); + return "del succ"; + } + + + /** + * del方法传值 + * + * @param id + * @return + */ + @DeleteMapping("testDel") + public String testDel(@RequestParam("id") String id) { + System.out.println("del id:" + id); + return "del succ"; + } + +} \ No newline at end of file diff --git a/quick-rest-template/src/main/java/com/rest/template/model/TestDTO.java b/quick-rest-template/src/main/java/com/rest/template/model/TestDTO.java new file mode 100644 index 00000000..448c1075 --- /dev/null +++ b/quick-rest-template/src/main/java/com/rest/template/model/TestDTO.java @@ -0,0 +1,13 @@ +package com.rest.template.model; + +import lombok.Data; + +/** + * @author vector + * @date: 2019/3/15 0015 9:22 + */ +@Data +public class TestDTO { + private int id; + private String name; +} diff --git a/quick-rest-template/src/main/java/com/rest/template/service/RestService.java b/quick-rest-template/src/main/java/com/rest/template/service/RestService.java new file mode 100644 index 00000000..a8ae406f --- /dev/null +++ b/quick-rest-template/src/main/java/com/rest/template/service/RestService.java @@ -0,0 +1,81 @@ +package com.rest.template.service; + +import com.alibaba.fastjson.JSON; +import com.rest.template.model.TestDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author vector + * @date: 2019/3/15 0015 9:26 + */ +@Service +public class RestService { + + @Autowired + private RestTemplate restTemplate; + + private static String HOST = "http://localhost:8080"; + + private static String GET_URL = "/testGet"; + private static String POST_URL = "/testPost"; + private static String POST_PARAM_URL = "/testPostParam"; + private static String PUT_URL = "/testPut"; + private static String DEL_URL = "/testDel"; + + + public void get() throws URISyntaxException { + ResponseEntity responseEntity = this.restTemplate.getForEntity(HOST + GET_URL, TestDTO.class); + System.out.println("getForEntity: " + responseEntity.getBody()); + + TestDTO forObject = this.restTemplate.getForObject(HOST + GET_URL, TestDTO.class); + System.out.println("getForObject: " + forObject); + + RequestEntity requestEntity = RequestEntity.get(new URI(HOST + GET_URL)).build(); + ResponseEntity exchange = this.restTemplate.exchange(requestEntity, TestDTO.class); + System.out.println("exchange: " + exchange.getBody()); + } + + public void post() throws URISyntaxException { + + TestDTO td = new TestDTO(); + td.setId(1); + td.setName("post"); + + String url = HOST + POST_URL; + HttpHeaders headers = new HttpHeaders(); + HttpEntity httpEntity = new HttpEntity<>(td, headers); + + + ResponseEntity responseEntity = this.restTemplate.postForEntity(url, httpEntity, TestDTO.class); + System.out.println("postForEntity: " + responseEntity.getBody()); + + TestDTO testDTO = this.restTemplate.postForObject(url, httpEntity, TestDTO.class); + System.out.println("postForObject: " + testDTO); + + ResponseEntity exchange = this.restTemplate.exchange(url, HttpMethod.POST, httpEntity, TestDTO.class); + System.out.println("exchange: " + exchange.getBody()); + } + + public void post4Form() { + String url = HOST + POST_PARAM_URL; + HttpHeaders headers = new HttpHeaders(); + MultiValueMap map= new LinkedMultiValueMap<>(); + map.add("id", "100"); + map.add("name", "post4Form"); + HttpEntity> request = new HttpEntity<>(map, headers); + + + ResponseEntity responseEntity = this.restTemplate.postForEntity(url, request, String.class); + System.out.println("postForEntity: " + responseEntity.getBody()); + } +} diff --git a/quick-rest-template/src/main/resources/application.yml b/quick-rest-template/src/main/resources/application.yml new file mode 100644 index 00000000..47fbb02d --- /dev/null +++ b/quick-rest-template/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 8080 \ No newline at end of file diff --git a/quick-rest-template/src/test/java/service/RestServiceTest.java b/quick-rest-template/src/test/java/service/RestServiceTest.java new file mode 100644 index 00000000..dff5a127 --- /dev/null +++ b/quick-rest-template/src/test/java/service/RestServiceTest.java @@ -0,0 +1,39 @@ +package service; + +import com.rest.template.Application; +import com.rest.template.service.RestService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.annotation.Resource; +import java.net.URISyntaxException; + +/** + * @author vector + * @date: 2019/3/15 0015 9:31 + */ +@SpringBootTest(classes = Application.class) +@RunWith(SpringJUnit4ClassRunner.class) +public class RestServiceTest { + + @Resource + private RestService restService; + + @Test + public void testGet() throws URISyntaxException { + restService.get(); + } + + @Test + public void testPost() throws URISyntaxException { + restService.post(); + } + + @Test + public void testPost4Form() { + restService.post4Form(); + } + +} diff --git a/quick-rocketmq/pom.xml b/quick-rocketmq/pom.xml new file mode 100644 index 00000000..1b2a4723 --- /dev/null +++ b/quick-rocketmq/pom.xml @@ -0,0 +1,67 @@ + + + quick-rocketmq + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + com.aliyun.openservices + ons-client + 1.7.0.Final + + + + + + com.alibaba + fastjson + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + \ No newline at end of file diff --git a/quick-rocketmq/src/main/java/com/rocketmq/Application.java b/quick-rocketmq/src/main/java/com/rocketmq/Application.java new file mode 100644 index 00000000..e2d9040b --- /dev/null +++ b/quick-rocketmq/src/main/java/com/rocketmq/Application.java @@ -0,0 +1,18 @@ +package com.rocketmq; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Created with IDEA + * User: vector + * Data: 2017/12/5 + * Time: 19:11 + * Description: + */ +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/quick-rocketmq/src/main/java/com/rocketmq/TestMain.java b/quick-rocketmq/src/main/java/com/rocketmq/TestMain.java new file mode 100644 index 00000000..6fdf3bdc --- /dev/null +++ b/quick-rocketmq/src/main/java/com/rocketmq/TestMain.java @@ -0,0 +1,16 @@ +package com.rocketmq; + +import java.util.UUID; + +/** + * Created with IDEA + * User: vector + * Data: 2017/12/5 + * Time: 20:18 + * Description: + */ +public class TestMain { + public static void main(String[] args) { + System.out.println(UUID.randomUUID().toString()); + } +} diff --git a/quick-rocketmq/src/main/java/com/rocketmq/controller/ApiController.java b/quick-rocketmq/src/main/java/com/rocketmq/controller/ApiController.java new file mode 100644 index 00000000..eace6c5d --- /dev/null +++ b/quick-rocketmq/src/main/java/com/rocketmq/controller/ApiController.java @@ -0,0 +1,28 @@ +package com.rocketmq.controller; + +import com.rocketmq.service.MQService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * Created with IDEA + * User: vector + * Data: 2017/12/5 + * Time: 19:43 + * Description: + */ +@RestController +public class ApiController { + + @Resource + private MQService mqService; + + @RequestMapping("/send") + public String sendMsg(@RequestParam("msg") String msg) { + mqService.sendMsg(msg); + return "hello world"; + } +} diff --git a/quick-rocketmq/src/main/java/com/rocketmq/service/MQService.java b/quick-rocketmq/src/main/java/com/rocketmq/service/MQService.java new file mode 100644 index 00000000..18fa21d1 --- /dev/null +++ b/quick-rocketmq/src/main/java/com/rocketmq/service/MQService.java @@ -0,0 +1,40 @@ +package com.rocketmq.service; + +import com.alibaba.fastjson.JSONObject; +import com.aliyun.openservices.ons.api.Message; +import com.aliyun.openservices.ons.api.SendResult; +import com.rocketmq.util.AliyunMessageProducer; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +/** + * Created with IDEA + * User: vector + * Data: 2017/12/5 + * Time: 19:12 + * Description: + */ +@Service +public class MQService { + +// @PostConstruct +// public void init() { +// AliyunMessageConsumer.subscribe(); +// } + + + public void sendMsg(String msg) { + String str[] = {"a78e001e-7ea2-41dc-926b-d8037cfd8286", "a78e001e-7ea2-4207-aab4-6356794fd145", "a78e001e-7ea2-4469-8df0-496408714ebd", "a78e001e-7ea2-4852-bb78-3bcd94a66631", "a78e001e-7ea2-4ad4-a266-b16fbd000024", "a78e001e-7ea2-4ad9-9191-a9d3dc90ecfe", "a78e001e-7ea2-4fee-9e58-acdd97f91647", "a78e001e-7ea3-408e-97f8-ca63068dfc11", "a78e001e-7ea3-417a-9b4d-58b5c31d4778", "a78e001e-7ea3-4224-82a5-4191919c18b8", "a78e001e-7ea3-49bd-b5dd-bc6c21b2aa6f", "a78e001e-7f5a-4571-8d86-a1bf64a4da4e", "a78e001e-7f5a-49cc-bf8d-9aaa4aa1e859", "a78e001e-802a-4e09-9bfa-ac9d0e6aefbb", "a78e001e-802a-4e0b-9162-6e8376eb5c96", "a78e001e-8063-44ad-a311-3fee1d94612d", "a78e001e-807b-4060-9a59-bd97793246f4", "a78e001e-807b-4725-8a2c-fa676c049740", "a78e001e-807c-4079-b558-485970470ad1", "a78e001e-8093-45a8-8c34-2b222dbdbab5", "a78e001e-80ec-49f7-89a2-7081a6432ffe", "a78e001e-80ed-41c6-978d-6c874ce5d03c", "a78e001e-8103-4015-a24e-fe195d09ebab", "a78e001e-8121-44a1-8500-1e0dbbd4444a", "a78e001e-8232-4422-8963-a342de924331", "a78e001e-8232-453b-aee6-99c6f1ca00b1", "a78e001e-8232-47a1-ab19-b9c9a884fc67", "a78e001e-82ec-472f-ae86-61b3da80603e", "a78e001e-82ec-4aaf-b2d4-d8fcc6cca015", "a78e001e-82ed-4070-8b21-96a8a762c681"}; + for (int i=0;i" +System.currentTimeMillis()); + /** + * 消息消费处理失败或者处理出现异常,返回OrderAction.Suspend
+ */ + return OrderAction.Success; + } +} diff --git a/quick-rocketmq/src/main/java/com/rocketmq/util/AliyunMessageProducer.java b/quick-rocketmq/src/main/java/com/rocketmq/util/AliyunMessageProducer.java new file mode 100644 index 00000000..cb2b77de --- /dev/null +++ b/quick-rocketmq/src/main/java/com/rocketmq/util/AliyunMessageProducer.java @@ -0,0 +1,76 @@ +package com.rocketmq.util; + +import com.aliyun.openservices.ons.api.*; +import com.aliyun.openservices.ons.api.order.OrderProducer; + +import java.util.Date; +import java.util.Properties; + +/** + * Created with IDEA + * User: vector + * Data: 2017/12/5 + * Time: 19:34 + * Description: + */ +public class AliyunMessageProducer { + + private static final String ACCESS_KEY = "LTAIcV7Ho2KS9a64"; + private static final String SECRET_KEY = "3FWmAyWC99S3D8a3iVYzBZ0qiD4fOJ"; + private static final String ONSAddr = "http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet"; + + public static OrderProducer producer; + + + + + /** + * 获取消息的 Producer + * 设置为单例 + * @param producerId producerId + * @return Producer + */ + public static OrderProducer getProducer(String producerId) { + if (producer == null) { + Properties properties = new Properties(); + properties.put(PropertyKeyConst.ProducerId, producerId); + properties.put(PropertyKeyConst.AccessKey, ACCESS_KEY); + properties.put(PropertyKeyConst.SecretKey, SECRET_KEY); + properties.setProperty(PropertyKeyConst.SendMsgTimeoutMillis, "3000"); + properties.put(PropertyKeyConst.ONSAddr,ONSAddr); + producer = ONSFactory.createOrderProducer(properties); + // 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可。 + producer.start(); + + + } + return producer; + } + + /** + * 发布消息 + * @param producerId + * @param msg + * @return + */ + public static SendResult sendMsg(String producerId,String sharding,Message msg) { + OrderProducer producer = getProducer(producerId); //你申请的producerId + SendResult sendResult = null; + try { + sendResult = producer.send(msg,sharding); + // 发送消息,只要不抛异常就是成功 + if (sendResult != null) { + System.out.println(new Date() + " Send mq message success. Topic is:" + msg.getTopic() + " msgId is: " + sendResult.getMessageId()); + } + } + catch (Exception e) { + // 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理 + System.out.println(new Date() + " Send mq message failed. Topic is:" + msg.getTopic()); + e.printStackTrace(); + } + return sendResult; + } + + + +} diff --git a/quick-rocketmq/src/main/resources/application.properties b/quick-rocketmq/src/main/resources/application.properties new file mode 100644 index 00000000..a3ac65ce --- /dev/null +++ b/quick-rocketmq/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8080 \ No newline at end of file diff --git a/quick-sample-server/README.md b/quick-sample-server/README.md new file mode 100644 index 00000000..8ee15c9b --- /dev/null +++ b/quick-sample-server/README.md @@ -0,0 +1,71 @@ +快速生成服务骨架 + +https://blog.csdn.net/u011731544/article/details/132322425 + +#### 创建maven骨架步骤 +1、在目录下执行命令 +```bash +spring-boot-quick\quick-archetype\mvn archetype:create-from-project +``` + +2、进入target中的archetype文件执行install +```bash +spring-boot-quick\quick-archetype\target\generated-sources\archetype> mvn install + +``` + +2.1、 修改文件名称,支持动态参数 +跟POM增加动态module声明 +```xml + + ${rootArtifactId}-api + ${rootArtifactId}-server + +``` +2.2、修改module文件名 +``` +__rootArtifactId__-api +``` + +2.3、修改启动文件名和类名 +``` +__appNameCap__Application.java + +public class ${appNameCap}Application { + public static void main(String[] args) { + SpringApplication.run(${appNameCap}Application.class); + } +} +``` +2.4、archetype-metadata.xml 修改 +``` + + +``` + +2.5、文件中的package需要替换 +``` +${packageInPathFormat} ---> ${package} +``` +| Variable | Meaning | | | | +|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|---|---|---| +| _rootArtifactId_ | 做文件夹名替换用,例如_rootArtifactId_-dao, 占位符来动态获取父工程的ArtifactId | | | | +| ${rootArtifactId} | 它保存用户输入的值作为项目名(maven在用户运行原型时在提示符中询问为artifactid:的值) | | | | +| ${artifactId} | 如果您的项目由一个模块组成,则此变量的值将与${rootArtifactId}相同,但如果项目包含多个模块,则此变量将由每个模块文件夹中的模块名替换 | | | | +| `${package} | 用户为项目提供的包,也在用户运行原型时由maven提示 | | | | +| ${packageInPathFormat} | 与${package}变量的值相同,但将“.”替换为字符“/”,例如:,对于包com.foo.bar,此变量为com/foo/bar | | | | +| ${groupId} | 用户为项目提供的groupid,在用户运行原型时由maven提示 | | | | +| ${version} | 版本号 | | | | + +3、执行archetype:crawl命令,用于构建骨架,在本地仓库的根目录生成archetype-catalog.xml骨架配置文件 + +4、为确认,可查看本仓库`archetype-catalog.xml`中内容是否有当前archetype声明节点,如下: +```xml + + com.quick + quick-sample-server-archetype + 1.0-SNAPSHOT + quick-sample-server + +``` + diff --git a/quick-sample-server/archetype.properties b/quick-sample-server/archetype.properties new file mode 100644 index 00000000..b7e1c283 --- /dev/null +++ b/quick-sample-server/archetype.properties @@ -0,0 +1,2 @@ +excludePatterns=.idea/**,README.md,.mvn/wrapper/*,.gitignore +archetype.filteredExtensions=java,yml,yaml,xml,txt,groovy,cs,mdo,aj,jsp,gsp,vm,html,xhtml,properties,.classpath,.project diff --git a/quick-sample-server/pom.xml b/quick-sample-server/pom.xml new file mode 100644 index 00000000..559d5e54 --- /dev/null +++ b/quick-sample-server/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + quick-sample-server + pom + + sample-api + sample-server + + + + + 8 + 8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + com.baomidou + mybatis-plus-boot-starter + + + com.baomidou + mybatis-plus-generator + + + + org.apache.velocity + velocity-engine-core + 2.3 + + + + mysql + mysql-connector-java + 8.0.26 + + + + + com.quick + quick-platform-common + 1.0-SNAPSHOT + + + com.quick.component + quick-platform-component + 1.0-SNAPSHOT + + + org.projectlombok + lombok + + + + + + + + + + org.apache.maven.plugins + maven-archetype-plugin + 2.0-alpha-4 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.2 + + UTF-8 + + + + + + \ No newline at end of file diff --git a/quick-sample-server/sample-api/pom.xml b/quick-sample-server/sample-api/pom.xml new file mode 100644 index 00000000..2237e5de --- /dev/null +++ b/quick-sample-server/sample-api/pom.xml @@ -0,0 +1,12 @@ + + 4.0.0 + + com.quick + quick-sample-server + 1.0-SNAPSHOT + + sample-api + Archetype - sample-api + http://maven.apache.org + diff --git a/quick-sample-server/sample-api/src/main/java/com/sample/api/SampleController.java b/quick-sample-server/sample-api/src/main/java/com/sample/api/SampleController.java new file mode 100644 index 00000000..fc51848c --- /dev/null +++ b/quick-sample-server/sample-api/src/main/java/com/sample/api/SampleController.java @@ -0,0 +1,14 @@ +package com.sample.api; + + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController("/api") +public class SampleController { + + @RequestMapping + public String hello() { + return "hello"; + } +} diff --git a/quick-sample-server/sample-server/pom.xml b/quick-sample-server/sample-server/pom.xml new file mode 100644 index 00000000..72a8e051 --- /dev/null +++ b/quick-sample-server/sample-server/pom.xml @@ -0,0 +1,12 @@ + + 4.0.0 + + com.quick + quick-sample-server + 1.0-SNAPSHOT + + sample-server + Archetype - sample-server + http://maven.apache.org + diff --git a/quick-sample-server/sample-server/src/main/java/com/quick/CustomApplication.java b/quick-sample-server/sample-server/src/main/java/com/quick/CustomApplication.java new file mode 100644 index 00000000..4ca97cc8 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/java/com/quick/CustomApplication.java @@ -0,0 +1,15 @@ +package com.quick; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +@SpringBootApplication +@MapperScan("com.quick.mapper") + +public class CustomApplication { + public static void main(String[] args) { + SpringApplication.run(CustomApplication.class); + } +} \ No newline at end of file diff --git a/quick-sample-server/sample-server/src/main/java/com/quick/controller/SampleTableController.java b/quick-sample-server/sample-server/src/main/java/com/quick/controller/SampleTableController.java new file mode 100644 index 00000000..73ee4034 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/java/com/quick/controller/SampleTableController.java @@ -0,0 +1,67 @@ +package com.quick.controller; + +import com.quick.entity.SampleTable; +import com.quick.service.SampleTableService; +import cn.hutool.core.lang.Assert; +import com.quick.common.base.rest.BaseResp; +import org.springframework.web.bind.annotation.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import com.quick.common.base.rest.BaseController; + + +/** + *

+ * 前端控制器 + *

+ * + * @author vector4wang + * @version ${cfg.version} + * @since 2023-10-17 + */ +@RestController +@RequestMapping(value = {"/sampleTable" }, + produces = MediaType.APPLICATION_JSON_VALUE) +public class SampleTableController implements BaseController { + + @Autowired + private SampleTableService sampleTableService; + + @ResponseBody + @RequestMapping(value = "/list", method = RequestMethod.POST) + public BaseResp list(@RequestBody SampleTable page) { + + return success(sampleTableService.pageList(page)); + } + + + @ResponseBody + @RequestMapping(value = "/{id}", method = RequestMethod.GET) + public BaseResp detail(@PathVariable String id) { + return success(sampleTableService.getById(id)); + } + + @ResponseBody + @RequestMapping(value = "save", method = RequestMethod.POST) + public BaseResp save(@RequestBody SampleTable sampleTable) { + sampleTableService.saveEntity(sampleTable); + return success(); + } + + @ResponseBody + @RequestMapping(value = "/{id}", method = RequestMethod.PUT) + public BaseResp update(@PathVariable String id, @RequestBody SampleTable sampleTable) { + Assert.notEmpty(id, "ID不能为空"); + sampleTableService.updateById(sampleTable); + return success(); + } + + + @ResponseBody + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) + public BaseResp delete(@PathVariable String id) { + sampleTableService.delete(id); + return success(); + } + +} diff --git a/quick-sample-server/sample-server/src/main/java/com/quick/entity/SampleTable.java b/quick-sample-server/sample-server/src/main/java/com/quick/entity/SampleTable.java new file mode 100644 index 00000000..4a6e7341 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/java/com/quick/entity/SampleTable.java @@ -0,0 +1,40 @@ +package com.quick.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; + + +/** + *

+ * + *

+ * + * @author vector4wang + * @version ${cfg.version} + * @since 2023-10-17 + */ +@Data +@Accessors(chain = true) +@TableName("sample_table") +public class SampleTable implements Serializable { + + private static final long serialVersionUID = 1L; + + + @TableId("id") + @TableField("id") + private Long id; + private String name; + private String userCode; + private String address; + private LocalDateTime createTime; + private LocalDateTime updateTime; + + +} diff --git a/quick-sample-server/sample-server/src/main/java/com/quick/mapper/SampleTableMapper.java b/quick-sample-server/sample-server/src/main/java/com/quick/mapper/SampleTableMapper.java new file mode 100644 index 00000000..cbe14793 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/java/com/quick/mapper/SampleTableMapper.java @@ -0,0 +1,18 @@ +package com.quick.mapper; + +import com.quick.entity.SampleTable; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springframework.stereotype.Repository; + +/** + *

+ * Mapper 接口 + *

+ * + * @author vector4wang + * @since 2023-10-17 + * @version ${cfg.version} + */ +@Repository +public interface SampleTableMapper extends BaseMapper { +} diff --git a/quick-sample-server/sample-server/src/main/java/com/quick/service/SampleTableService.java b/quick-sample-server/sample-server/src/main/java/com/quick/service/SampleTableService.java new file mode 100644 index 00000000..d04573cd --- /dev/null +++ b/quick-sample-server/sample-server/src/main/java/com/quick/service/SampleTableService.java @@ -0,0 +1,27 @@ +package com.quick.service; + +import com.quick.entity.SampleTable; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author vector4wang + * @since 2023-10-17 + * @version ${cfg.version} + */ +public interface SampleTableService extends IService { + + + List pageList(SampleTable sampleTable); + + boolean saveEntity(SampleTable sampleTable); + + void delete(String id); + + +} diff --git a/quick-sample-server/sample-server/src/main/java/com/quick/service/impl/SampleTableServiceImp.java b/quick-sample-server/sample-server/src/main/java/com/quick/service/impl/SampleTableServiceImp.java new file mode 100644 index 00000000..1b2cdf60 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/java/com/quick/service/impl/SampleTableServiceImp.java @@ -0,0 +1,38 @@ +package com.quick.service.impl; + +import com.quick.entity.SampleTable; +import com.quick.mapper.SampleTableMapper; +import com.quick.service.SampleTableService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

+ * 服务实现类 + *

+ * + * @author vector4wang + * @version ${cfg.version} + * @since 2023-10-17 + */ +@Service +public class SampleTableServiceImp extends ServiceImpl implements SampleTableService { + + @Override + public boolean saveEntity(SampleTable sampleTable) { + this.saveOrUpdate(sampleTable); + return true; + } + + @Override + public void delete(String id) { + this.removeById(id); + } + + @Override + public List pageList(SampleTable sampleTable) { + return this.list(); + } +} diff --git a/quick-sample-server/sample-server/src/main/java/com/quick/utils/BaseGenerator.java b/quick-sample-server/sample-server/src/main/java/com/quick/utils/BaseGenerator.java new file mode 100644 index 00000000..8561221c --- /dev/null +++ b/quick-sample-server/sample-server/src/main/java/com/quick/utils/BaseGenerator.java @@ -0,0 +1,26 @@ +package com.quick.utils; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.setting.dialect.Props; +import com.baomidou.mybatisplus.generator.config.DataSourceConfig; + +import java.io.IOException; + +public class BaseGenerator { + /** + * 数据库配置 + * + * @return 数据源 + */ + public static DataSourceConfig.Builder dataSourceGenerate() throws IOException { + + Props properties = new Props("application-jdbc.properties", CharsetUtil.CHARSET_UTF_8); + //获取properties里的参数 + String url = properties.getStr("spring.datasource.url"); + String driver = properties.getStr("spring.datasource.driver-class-name"); + String username = properties.getStr("spring.datasource.username"); + String password = properties.getStr("spring.datasource.password"); + // 数据库配置 + return new DataSourceConfig.Builder(url, username, password); + } +} diff --git a/quick-sample-server/sample-server/src/main/java/com/quick/utils/MyBatisPlusGenerator.java b/quick-sample-server/sample-server/src/main/java/com/quick/utils/MyBatisPlusGenerator.java new file mode 100644 index 00000000..48fe1b55 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/java/com/quick/utils/MyBatisPlusGenerator.java @@ -0,0 +1,66 @@ +package com.quick.utils; + +import com.baomidou.mybatisplus.generator.FastAutoGenerator; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class MyBatisPlusGenerator extends BaseGenerator { + + public static final String OUT_DIR = "D:\\github\\spring-boot-quick\\quick-archetype\\src\\main\\java"; + + + // 处理 all 情况 + protected static List getTables(String tables) { + return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(",")); + } + + public static void main(String[] args) throws IOException { + FastAutoGenerator.create(dataSourceGenerate()) + // 全局配置 + .globalConfig((scanner, builder) -> { + builder.author(scanner.apply("请输入作者")).fileOverride(); + builder.outputDir(OUT_DIR); + }) + // 包配置 + .packageConfig((scanner, builder) -> { + builder +// .pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir") + "/src/main/resources/mapper")) + .parent(scanner.apply("请输入包名?")); + }) + // 策略配置 + .strategyConfig((scanner, builder) -> { + builder.addInclude(MyBatisPlusGenerator.getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all"))) + .controllerBuilder() + .enableRestStyle() + .enableHyphenStyle() + .build(); + + builder.serviceBuilder() + .formatServiceFileName("%sService") + .formatServiceImplFileName("%sServiceImp") + .build(); + //entity的策略配置 + builder.entityBuilder() + .enableLombok() + .enableTableFieldAnnotation() + .versionColumnName("version") + .logicDeleteColumnName("is_delete") + .columnNaming(NamingStrategy.underline_to_camel) +// .idType(IdType.AUTO) + .formatFileName("%sEntity") + .build(); + + // mapper xml配置 + builder.mapperBuilder() + .formatMapperFileName("%sMapper") + .enableBaseColumnList() + .enableBaseResultMap() + .build(); + }) + .execute(); + } +} diff --git a/quick-sample-server/sample-server/src/main/java/com/quick/utils/MyBatisPlusWithTemplateGenerator.java b/quick-sample-server/sample-server/src/main/java/com/quick/utils/MyBatisPlusWithTemplateGenerator.java new file mode 100644 index 00000000..45d06fc3 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/java/com/quick/utils/MyBatisPlusWithTemplateGenerator.java @@ -0,0 +1,88 @@ +package com.quick.utils; + +import com.baomidou.mybatisplus.generator.FastAutoGenerator; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class MyBatisPlusWithTemplateGenerator extends BaseGenerator { + + // TODO + public static final String OUT_DIR = "D:\\develop\\code\\temp"; + + + // 处理 all 情况 + protected static List getTables(String tables) { + return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(",")); + } + + + public static void main(String[] args) throws IOException { + FastAutoGenerator.create(dataSourceGenerate()) + // 全局配置 + .globalConfig((scanner, builder) -> { + builder.author(scanner.apply("请输入作者")) + .fileOverride() + .outputDir(OUT_DIR); + + }) + // 包配置 + .packageConfig((scanner, builder) -> { + builder +// .pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir") + "/src/main/resources/mapper")) + .parent(scanner.apply("请输入包名?")); + }) + // 策略配置 + .strategyConfig((scanner, builder) -> { + builder.addInclude(MyBatisPlusWithTemplateGenerator.getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all"))) + .controllerBuilder() + .enableRestStyle() + .enableHyphenStyle() + .superClass("com.quick.common.base.rest.BaseController") + .build(); + + builder.serviceBuilder() + .formatServiceFileName("%sService") + .formatServiceImplFileName("%sServiceImp") + .build(); + //entity的策略配置 + builder.entityBuilder() + .enableLombok() + .enableTableFieldAnnotation() + .versionColumnName("version") +// .logicDeleteColumnName("is_delete") + .columnNaming(NamingStrategy.underline_to_camel) +// .idType(IdType.AUTO) + .formatFileName("%s") + .build(); + + // mapper xml配置 + builder.mapperBuilder() + .formatMapperFileName("%sMapper") + .superClass("com.baomidou.mybatisplus.core.mapper.BaseMapper") + .enableBaseColumnList() + .enableBaseResultMap() + .build(); + }) + .templateEngine(new VelocityTemplateEngine()) + .templateConfig(builder -> { + builder.entity("/templates/entity.java.vm") + .service("/templates/service.java.vm") + .serviceImpl("/templates/serviceImpl.java.vm") + .controller("/templates/controller.java.vm"); + }) + //注入配置————自定义模板 +// .injectionConfig(builder -> builder +// .beforeOutputFile((tableInfo, objectMap) -> { +// System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size()); +// }) //输出文件之前消费者 +// .customMap(Collections.singletonMap("my_field", "自定义配置 Map 对象")) //自定义配置 Map 对象 +// .customFile(Collections.singletonMap("query.java", "/templates/query.java.vm")) //自定义配置模板文件 +// .build())//加入构建队列 + .execute(); + } +} diff --git a/quick-sample-server/sample-server/src/main/resources/application-jdbc.properties b/quick-sample-server/sample-server/src/main/resources/application-jdbc.properties new file mode 100644 index 00000000..a8f326e3 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/resources/application-jdbc.properties @@ -0,0 +1,12 @@ +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/cg?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true +spring.datasource.username=root +spring.datasource.password=123456 + + +mybatis-plus.configuration.map-underscore-to-camel-case=true +mybatis-plus.configuration.auto-mapping-behavior=full +mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl +mybatis-plus.mapper-locations=classpath*:mapper/**/*Mapper.xml +mybatis-plus.global-config.db-config.logic-not-delete-value=0 +mybatis-plus.global-config.db-config.logic-delete-value=1 \ No newline at end of file diff --git a/quick-sample-server/sample-server/src/main/resources/application.properties b/quick-sample-server/sample-server/src/main/resources/application.properties new file mode 100644 index 00000000..f8d06a5f --- /dev/null +++ b/quick-sample-server/sample-server/src/main/resources/application.properties @@ -0,0 +1,3 @@ +server.port=8080 + +spring.profiles.include=jdbc \ No newline at end of file diff --git a/quick-sample-server/sample-server/src/main/resources/mapper/SampleTableMapper.xml b/quick-sample-server/sample-server/src/main/resources/mapper/SampleTableMapper.xml new file mode 100644 index 00000000..fc794f6a --- /dev/null +++ b/quick-sample-server/sample-server/src/main/resources/mapper/SampleTableMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + id, name, user_code, address, create_time, update_time + + + diff --git a/quick-sample-server/sample-server/src/main/resources/templates/controller.java.vm b/quick-sample-server/sample-server/src/main/resources/templates/controller.java.vm new file mode 100644 index 00000000..c0ce9432 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/resources/templates/controller.java.vm @@ -0,0 +1,70 @@ +package ${package.Controller}; + +import ${package.Entity}.${entity}; +import ${package.Service}.${table.serviceName}; +import cn.hutool.core.lang.Assert; +import com.quick.common.base.rest.BaseResp; +import org.springframework.web.bind.annotation.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +#if(${superControllerClassPackage}) +import $!{superControllerClassPackage}; +#end + + +/** + *

+ * $!{table.comment} 前端控制器 + *

+ * + * @author ${author} + * @since ${date} + * @version ${cfg.version} + */ +#set($entityName = ${entity.substring(0,1).toLowerCase()}+${entity.substring(1)}) +@RestController +@RequestMapping(value = {"/${table.entityPath}" }, + produces = MediaType.APPLICATION_JSON_VALUE) +public class ${table.controllerName} implements ${superControllerClass} { + + @Autowired + private ${table.serviceName} ${entityName}Service; + + @ResponseBody + @RequestMapping(value = "/list", method = RequestMethod.POST) + public BaseResp list(@RequestBody ${entity} page){ + + return success(${entityName}Service.pageList(page)); + } + + + @ResponseBody + @RequestMapping(value = "/{id}", method = RequestMethod.GET) + public BaseResp<${entity}> detail(@PathVariable String id) { + return success(${entityName}Service.getById(id)); + } + + @ResponseBody + @RequestMapping(value = "save", method = RequestMethod.POST) + public BaseResp save(@RequestBody ${entity} $entityName){ + ${entityName}Service.saveEntity($entityName); + return success(); + } + + @ResponseBody + @RequestMapping(value = "/{id}", method = RequestMethod.PUT) + public BaseResp update(@PathVariable String id,@RequestBody ${entity} $entityName){ + Assert.notEmpty(id,"ID不能为空"); + ${entityName}Service.updateById($entityName); + return success(); + } + + + @ResponseBody + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) + public BaseResp delete(@PathVariable String id){ + ${entityName}Service.delete(id); + return success(); + } + +} diff --git a/quick-sample-server/sample-server/src/main/resources/templates/entity.java.vm b/quick-sample-server/sample-server/src/main/resources/templates/entity.java.vm new file mode 100644 index 00000000..72320aff --- /dev/null +++ b/quick-sample-server/sample-server/src/main/resources/templates/entity.java.vm @@ -0,0 +1,154 @@ +package ${package.Entity}; + +#foreach($pkg in ${table.importPackages}) +import ${pkg}; +#end + +#if(${entityLombokModel}) + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +#end +#if(${swagger2}) +import io.swagger.annotations.ApiModelProperty; +#end + + +/** + *

+ * $!{table.comment} + *

+ * + * @author ${author} + * @since ${date} + * @version ${cfg.version} + */ +#if(${entityLombokModel}) +@Data +@Accessors(chain = true) +#end +@TableName("${table.name}") +#if(${superEntityClass}) +public class ${entity} extends ${superEntityClass}#if(${activeRecord})<${entity}>#end { +#elseif(${activeRecord}) +public class ${entity} extends Model<${entity}> { +#else +public class ${entity} implements Serializable { +#end + +private static final long serialVersionUID = 1L; + +## ---------- BEGIN 字段循环遍历 ---------- + +## ---------- START 字段循环遍历 ---------- +#foreach($field in ${table.fields}) + #if(${field.keyFlag}) + #set($keyPropertyName=${field.propertyName}) + #end + #if("$!field.comment" != "") + #if(${swagger2}) + /** + * ${field.comment} + */ + @ApiModelProperty(value = "${field.comment}") + #else + /** + * ${field.comment} + */ + #end + #end + ## 主键 + #if(${field.keyFlag}) + #if(${field.keyIdentityFlag}) + @TableId(value = "${field.name}", type = IdType.AUTO) + #elseif(!$null.isNull(${idType}) && "$!idType" != "") + @TableId(value = "${field.name}", type = IdType.${idType}) + #elseif(${field.convert}) + @TableId("${field.name}") + #end + ## 普通字段 + #if(${field.fill}) + ## ----- 存在字段填充设置 ----- + #if(${field.convert}) + @TableField(value = "${field.name}", fill = FieldFill.${field.fill}) + #else + @TableField(fill = FieldFill.${field.fill}) + #end + #elseif(${field.convert}) + @TableField("${field.name}") + #end + #end + ## 时间类型 + #if(${field.name} == ${cfg.createTime}) + @TableField(fill = FieldFill.INSERT) + #elseif(${field.name} == ${cfg.updateTime}) + @TableField(fill = FieldFill.INSERT_UPDATE) + #end + ## 逻辑删除注解 + #if(${cfg.logicDeleteFieldName}==${field.name}) + @TableLogic + #end +private ${field.propertyType} ${field.propertyName}; +#end +## ---------- END 字段循环遍历 ---------- + +#if(!${entityLombokModel}) + #foreach($field in ${table.fields}) + #if(${field.propertyType.equals("boolean")}) + #set($getprefix="is") + #else + #set($getprefix="get") + #end + + public ${field.propertyType} ${getprefix}${field.capitalName}() { + return ${field.propertyName}; + } + + #if(${entityBuilderModel}) + public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) { + #else + public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) { + #end + this.${field.propertyName} = ${field.propertyName}; + #if(${entityBuilderModel}) + return this; + #end + } + #end + ## --foreach end--- +#end +## --end of + +#if(${entityColumnConstant}) + #foreach($field in ${table.fields}) + public static final String ${field.name.toUpperCase()} = "${field.name}"; + + #end +#end +#if(${activeRecord}) +@Override +protected Serializable pkVal() { + #if(${keyPropertyName}) + return this.${keyPropertyName}; + #else + return this.id; + #end + } + +#end +#if(!${entityLombokModel}) +@Override +public String toString() { + return "${entity}{" + + #foreach($field in ${table.fields}) + #if($!{velocityCount}==1) + "${field.propertyName}=" + ${field.propertyName} + + #else + ", ${field.propertyName}=" + ${field.propertyName} + + #end + #end + "}"; + } +#end + } diff --git a/quick-sample-server/sample-server/src/main/resources/templates/mapper.java.vm b/quick-sample-server/sample-server/src/main/resources/templates/mapper.java.vm new file mode 100644 index 00000000..46b5c81d --- /dev/null +++ b/quick-sample-server/sample-server/src/main/resources/templates/mapper.java.vm @@ -0,0 +1,18 @@ +package ${package.Mapper}; + +import ${package.Entity}.${entity}; +import ${superMapperClassPackage}; +import org.springframework.stereotype.Repository; + +/** + *

+ * $!{table.comment} Mapper 接口 + *

+ * + * @author ${author} + * @since ${date} + * @version ${cfg.version} + */ +@Repository +public interface ${table.mapperName} extends ${superMapperClass}<${entity}> { +} diff --git a/quick-sample-server/sample-server/src/main/resources/templates/mapper.xml.vm b/quick-sample-server/sample-server/src/main/resources/templates/mapper.xml.vm new file mode 100644 index 00000000..1af4ea1e --- /dev/null +++ b/quick-sample-server/sample-server/src/main/resources/templates/mapper.xml.vm @@ -0,0 +1,39 @@ + + + + + #if(${enableCache}) + + + + #end + #if(${baseResultMap}) + + + #foreach($field in ${table.fields}) + #if(${field.keyFlag})##生成主键排在第一位 + + #end + #end + #foreach($field in ${table.commonFields})##生成公共字段 + + #end + #foreach($field in ${table.fields}) + #if(!${field.keyFlag})##生成普通字段 + + #end + #end + + + #end + #if(${baseColumnList}) + + + #foreach($field in ${table.commonFields}) + #if(${field.name} == ${field.propertyName})${field.name}#else${field.name} AS ${field.propertyName}#end, + #end + ${table.fieldNames} + + + #end + diff --git a/quick-sample-server/sample-server/src/main/resources/templates/service.java.vm b/quick-sample-server/sample-server/src/main/resources/templates/service.java.vm new file mode 100644 index 00000000..9e680f42 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/resources/templates/service.java.vm @@ -0,0 +1,26 @@ +package ${package.Service}; + +import ${package.Entity}.${entity}; +import ${superServiceClassPackage}; + +/** + *

+ * $!{table.comment} 服务类 + *

+ * + * @author ${author} + * @since ${date} + * @version ${cfg.version} + */ +#set($entityName = ${entity.substring(0,1).toLowerCase()}+${entity.substring(1)}) +public interface ${table.serviceName} extends ${superServiceClass}<${entity}> { + + + List<${entity}> pageList(${entity} $entityName); + + boolean saveEntity(${entity} $entityName); + + void delete(String id); + + +} diff --git a/quick-sample-server/sample-server/src/main/resources/templates/serviceImpl.java.vm b/quick-sample-server/sample-server/src/main/resources/templates/serviceImpl.java.vm new file mode 100644 index 00000000..96805596 --- /dev/null +++ b/quick-sample-server/sample-server/src/main/resources/templates/serviceImpl.java.vm @@ -0,0 +1,37 @@ +package ${package.ServiceImpl}; + +import ${package.Entity}.${entity}; +import ${package.Mapper}.${table.mapperName}; +import ${package.Service}.${table.serviceName}; +import ${superServiceImplClassPackage}; +import org.springframework.stereotype.Service; + +/** + *

+ * $!{table.comment} 服务实现类 + *

+ * + * @author ${author} + * @since ${date} + * @version ${cfg.version} + */ +#set($entityName = ${entity.substring(0,1).toLowerCase()}+${entity.substring(1)}) +@Service +public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> implements ${table.serviceName} { + + @Override + public boolean saveEntity(${entity} $entityName) { + this.saveOrUpdate(sampleTable); + return true; + } + + @Override + public void delete(String id) { + this.removeById(id); + } + + @Override + public List pageList(${entity} $entityName) { + return this.list(); + } +} diff --git a/quick-sample-server/sample-server/src/test/java/TestMain.java b/quick-sample-server/sample-server/src/test/java/TestMain.java new file mode 100644 index 00000000..d3e6234e --- /dev/null +++ b/quick-sample-server/sample-server/src/test/java/TestMain.java @@ -0,0 +1,24 @@ +import com.quick.CustomApplication; +import com.quick.entity.SampleTable; +import com.quick.service.SampleTableService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@SpringBootTest(classes = CustomApplication.class) +public class TestMain { + @Autowired + private SampleTableService sampleTableService; + + @Test + public void testSelect() { + List list = sampleTableService.list(); + Map listMap = list.stream().collect(Collectors.toMap(SampleTable::getUserCode, item -> item, (o1, o2) -> o1)); + + System.out.println(listMap); + } +} diff --git a/quick-saturn/pom.xml b/quick-saturn/pom.xml new file mode 100644 index 00000000..ed8dc768 --- /dev/null +++ b/quick-saturn/pom.xml @@ -0,0 +1,67 @@ + + + quick-saturn + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + + org.springframework.boot + spring-boot-starter-parent + 2.0.1.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + 3.5.1 + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-starter-web + + + + com.vip.saturn + saturn-job-api + ${saturn.version} + + + + + + + + + + org.projectlombok + lombok + 1.18.2 + + + + com.google.code.gson + gson + + + + + + + com.vip.saturn + saturn-plugin + ${saturn.version} + + + + + + \ No newline at end of file diff --git a/quick-saturn/src/main/java/com/quick/saturn/SaturnApplication.java b/quick-saturn/src/main/java/com/quick/saturn/SaturnApplication.java new file mode 100644 index 00000000..7226c525 --- /dev/null +++ b/quick-saturn/src/main/java/com/quick/saturn/SaturnApplication.java @@ -0,0 +1,11 @@ +package com.quick.saturn; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SaturnApplication { + public static void main(String[] args) { + SpringApplication.run(SaturnApplication.class); + } +} diff --git a/quick-saturn/src/main/java/com/quick/saturn/job/SimpleJob.java b/quick-saturn/src/main/java/com/quick/saturn/job/SimpleJob.java new file mode 100644 index 00000000..f312e930 --- /dev/null +++ b/quick-saturn/src/main/java/com/quick/saturn/job/SimpleJob.java @@ -0,0 +1,30 @@ +package com.quick.saturn.job; + +import com.google.gson.Gson; +import com.vip.saturn.job.AbstractSaturnJavaJob; +import com.vip.saturn.job.SaturnJobExecutionContext; +import com.vip.saturn.job.SaturnJobReturn; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class SimpleJob extends AbstractSaturnJavaJob { + + private static Gson gson = new Gson(); + + /** + * @param jobName 作业名 + * @param shardItem 分片项 + * @param shardParam 分片参数 + * @param shardingContext 其它参数信息 + * @return 返回执行结果 + * @throws InterruptedException 注意处理中断异常 + */ + @Override + public SaturnJobReturn handleJavaJob(String jobName, Integer shardItem, String shardParam, + SaturnJobExecutionContext shardingContext) throws InterruptedException { + log.info("SimpleJob jobName:{}, shardItem:{}, shardParam:{}, shardingContext:{}", jobName, shardItem, shardParam, gson.toJson(shardingContext)); + return new SaturnJobReturn(shardItem + "end"); + } +} diff --git a/quick-saturn/src/main/resources/application.yml b/quick-saturn/src/main/resources/application.yml new file mode 100644 index 00000000..223a8beb --- /dev/null +++ b/quick-saturn/src/main/resources/application.yml @@ -0,0 +1,5 @@ +server: + port: 8080 + +app: + class: com.quick.saturn.SaturnApplication \ No newline at end of file diff --git a/quick-sharding-jdbc/pom.xml b/quick-sharding-jdbc/pom.xml new file mode 100644 index 00000000..ee89d351 --- /dev/null +++ b/quick-sharding-jdbc/pom.xml @@ -0,0 +1,62 @@ + + + quick-sharding-jdbc + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + 8 + 8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + + + com.alibaba + druid-spring-boot-starter + + + mysql + mysql-connector-java + + + + + org.apache.shardingsphere + sharding-jdbc-spring-boot-starter + + + com.baomidou + mybatis-plus-boot-starter + + + + com.baomidou + mybatis-plus-generator + + + + org.projectlombok + lombok + + + + \ No newline at end of file diff --git a/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/ShardingJdbcApplication.java b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/ShardingJdbcApplication.java new file mode 100644 index 00000000..c86eae4e --- /dev/null +++ b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/ShardingJdbcApplication.java @@ -0,0 +1,17 @@ +package com.quick.shardingjdbc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2022/8/16 22:31 + * + */ +@SpringBootApplication +public class ShardingJdbcApplication { + public static void main(String[] args) { + SpringApplication.run(ShardingJdbcApplication.class, args); + } +} diff --git a/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/entity/Address.java b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/entity/Address.java new file mode 100644 index 00000000..5f29735f --- /dev/null +++ b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/entity/Address.java @@ -0,0 +1,39 @@ +package com.quick.shardingjdbc.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * + *

+ * + * @author vector + * @since 2022-08-16 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("address") +public class Address implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + private Integer userId; + + private String address; + + private Date createTime; + + +} diff --git a/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/mapper/AddressMapper.java b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/mapper/AddressMapper.java new file mode 100644 index 00000000..e1fbbd8e --- /dev/null +++ b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/mapper/AddressMapper.java @@ -0,0 +1,16 @@ +package com.quick.shardingjdbc.mapper; + +import com.quick.shardingjdbc.entity.Address; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author vector + * @since 2022-08-16 + */ +public interface AddressMapper extends BaseMapper
{ + +} diff --git a/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/service/IAddressService.java b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/service/IAddressService.java new file mode 100644 index 00000000..d1d9d847 --- /dev/null +++ b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/service/IAddressService.java @@ -0,0 +1,16 @@ +package com.quick.shardingjdbc.service; + +import com.quick.shardingjdbc.entity.Address; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 服务类 + *

+ * + * @author vector + * @since 2022-08-16 + */ +public interface IAddressService extends IService
{ + +} diff --git a/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/service/impl/AddressServiceImpl.java b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/service/impl/AddressServiceImpl.java new file mode 100644 index 00000000..cdc9e6cf --- /dev/null +++ b/quick-sharding-jdbc/src/main/java/com/quick/shardingjdbc/service/impl/AddressServiceImpl.java @@ -0,0 +1,20 @@ +package com.quick.shardingjdbc.service.impl; + +import com.quick.shardingjdbc.entity.Address; +import com.quick.shardingjdbc.mapper.AddressMapper; +import com.quick.shardingjdbc.service.IAddressService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 服务实现类 + *

+ * + * @author vector + * @since 2022-08-16 + */ +@Service +public class AddressServiceImpl extends ServiceImpl implements IAddressService { + +} diff --git a/quick-sharding-jdbc/src/main/resources/application.properties b/quick-sharding-jdbc/src/main/resources/application.properties new file mode 100644 index 00000000..24c0fca5 --- /dev/null +++ b/quick-sharding-jdbc/src/main/resources/application.properties @@ -0,0 +1,44 @@ + +props.s + +# \u5B9A\u4E49\u4E24\u4E2A\u5168\u5C40\u6570\u636E\u6E90 +spring.shardingsphere.datasource.names=shardingjdbc2,shardingjdbc + +spring.main.allow-bean-definition-overriding=true + +#???????????????????????????? +spring.shardingsphere.datasource.shardingjdbc.type=com.alibaba.druid.pool.DruidDataSource +spring.shardingsphere.datasource.shardingjdbc.driver-class-name=com.mysql.jdbc.Driver +spring.shardingsphere.datasource.shardingjdbc.jdbc-url=jdbc:mysql://127.0.0.1:3306/shardingjdbc?autoReconnect=true&allowMultiQueries=true +spring.shardingsphere.datasource.shardingjdbc.username=root +spring.shardingsphere.datasource.shardingjdbc.password=123456 + +#???????????????????????????? +spring.shardingsphere.datasource.shardingjdbc2.type=com.alibaba.druid.pool.DruidDataSource +spring.shardingsphere.datasource.shardingjdbc2.driver-class-name=com.mysql.jdbc.Driver +spring.shardingsphere.datasource.shardingjdbc2.jdbc-url=jdbc:mysql://127.0.0.1:3306/shardingjdbc2?autoReconnect=true&allowMultiQueries=true +spring.shardingsphere.datasource.shardingjdbc2.username=root +spring.shardingsphere.datasource.shardingjdbc2.password=123456 + +# \u914D\u7F6E\u5206\u7247\u8868 t_order +# \u6307\u5B9A\u771F\u5B9E\u6570\u636E\u8282\u70B9 +spring.shardingsphere.sharding.tables.address.actual-data-nodes=shardingjdbc$->{0..1}.t_order_$->{0..2} +### \u5206\u5E93\u7B56\u7565 +# \u5206\u5E93\u5206\u7247\u5065 +spring.shardingsphere.sharding.tables.address.database-strategy.inline.sharding-column=address +# \u5206\u5E93\u5206\u7247\u7B97\u6CD5 +spring.shardingsphere.sharding.tables.address.database-strategy.inline.algorithm-expression=shardingjdbc$->{address % 2} +# \u5206\u8868\u7B56\u7565 +# \u5206\u8868\u5206\u7247\u5065 +spring.shardingsphere.sharding.tables.address.table-strategy.inline.sharding-column=address +# \u5206\u8868\u7B97\u6CD5 +spring.shardingsphere.sharding.tables.address.table-strategy.inline.algorithm-expression=t_order_$->{order_id % 3} +# \u81EA\u589E\u4E3B\u952E\u5B57\u6BB5 +spring.shardingsphere.sharding.tables.address.key-generator.column=order_id +# \u81EA\u589E\u4E3B\u952EID \u751F\u6210\u65B9\u6848 +spring.shardingsphere.sharding.tables.address.key-generator.type=SNOWFLAKE +# \u5DE5\u4F5C\u673A\u5668\u552F\u4E00 id +spring.shardingsphere.sharding.tables.address.key-generator.props.worker.id=0000 +# +spring.shardingsphere.sharding.tables.address.key-generator.max.tolerate.time.difference.milliseconds=5 +spring.shardingsphere.props.sql.show=true diff --git a/quick-sharding-jdbc/src/main/resources/mapper/AddressMapper.xml b/quick-sharding-jdbc/src/main/resources/mapper/AddressMapper.xml new file mode 100644 index 00000000..3c5aca88 --- /dev/null +++ b/quick-sharding-jdbc/src/main/resources/mapper/AddressMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/quick-shiro-cas/README.md b/quick-shiro-cas/README.md new file mode 100644 index 00000000..b0e9c9e8 --- /dev/null +++ b/quick-shiro-cas/README.md @@ -0,0 +1,27 @@ +#### 集成 CAS + +docker安装cas-server + +参考: https://apereo.github.io/cas/development/installation/Docker-Installation.html +```docker +docker pull apereo/cas:v5.3.10 + +docker run --name cas -p 8443:8443 -p 8442:8080 -d apereo/cas:v5.3.10 /bin/sh /cas-overlay/bin/run-cas.sh + +## 生成ssl证书 其中密钥库命令输入的是changeit +keytool -genkey -alias tomcat -keypass changeit -keyalg RSA -keystore server.keystore +## 复制 +docker cp server.keystore cas:/etc/cas/thekeystore + +``` + +访问:https://localhost:8443/cas/login + +用户名:casuser +密码:Mellon + +## 参照以下网址搭建cas服务端用于验证 +https://github.com/willwu1984/springboot-cas-shiro + + +后续了解流程即可 \ No newline at end of file diff --git a/quick-shiro-cas/pom.xml b/quick-shiro-cas/pom.xml new file mode 100644 index 00000000..9ab1eba0 --- /dev/null +++ b/quick-shiro-cas/pom.xml @@ -0,0 +1,82 @@ + + 4.0.0 + quick-shiro-cas + jar + + quick-shiro-cas + http://maven.apache.org + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + UTF-8 + 1.7 + 1.7 + + 4.0.0 + 3.3.0 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.shiro + shiro-web + + + + org.apache.shiro + shiro-ehcache + + + + org.apache.shiro + shiro-spring + + + + io.buji + buji-pac4j + ${buji.version} + + + org.pac4j + pac4j-cas + ${pac4j.version} + + + + org.projectlombok + lombok + + + + + + + mvnrepository + mvnrepository + https://repo1.maven.org/maven2// + default + + true + + + false + + + + diff --git a/quick-shiro-cas/src/main/java/com/shiro/ShiroCasApplication.java b/quick-shiro-cas/src/main/java/com/shiro/ShiroCasApplication.java new file mode 100644 index 00000000..d6820755 --- /dev/null +++ b/quick-shiro-cas/src/main/java/com/shiro/ShiroCasApplication.java @@ -0,0 +1,16 @@ +package com.shiro; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Hello world! + * + */ +@SpringBootApplication +public class ShiroCasApplication { + + public static void main(String[] args) { + SpringApplication.run(ShiroCasApplication.class, args); + } +} diff --git a/quick-shiro-cas/src/main/java/com/shiro/config/CasRealm.java b/quick-shiro-cas/src/main/java/com/shiro/config/CasRealm.java new file mode 100644 index 00000000..e9359e3c --- /dev/null +++ b/quick-shiro-cas/src/main/java/com/shiro/config/CasRealm.java @@ -0,0 +1,69 @@ +package com.shiro.config; + +import io.buji.pac4j.realm.Pac4jRealm; +import io.buji.pac4j.subject.Pac4jPrincipal; +import io.buji.pac4j.token.Pac4jToken; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.SimplePrincipalCollection; +import org.pac4j.core.profile.CommonProfile; + +import java.util.List; + +/** + * + * @author wangxc + * @date: 2022/9/5 21:18 + * + */ +@Slf4j +public class CasRealm extends Pac4jRealm { + + private String clientName; + + public String getClientName() { + return clientName; + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + + /** + * 认证 + * + * @param authenticationToken + * @return + * @throws AuthenticationException + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)throws + AuthenticationException { + final Pac4jToken pac4jToken = (Pac4jToken) authenticationToken; + final List commonProfileList = pac4jToken.getProfiles(); + final CommonProfile commonProfile = commonProfileList.get(0); + log.info("单点登录返回的信息" + commonProfile.toString()); + final Pac4jPrincipal principal = new Pac4jPrincipal(commonProfileList,getPrincipalNameAttribute()); + final PrincipalCollection principalCollection = new SimplePrincipalCollection(principal, getName()); + return new SimpleAuthenticationInfo(principalCollection,commonProfileList.hashCode()); + } + + /** + * 授权/验权(todo 后续有权限在此增加) + * + * @param principals + * @return + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo(); + authInfo.addStringPermission("user"); + return authInfo; + } +} diff --git a/quick-shiro-cas/src/main/java/com/shiro/config/Pac4jConfig.java b/quick-shiro-cas/src/main/java/com/shiro/config/Pac4jConfig.java new file mode 100644 index 00000000..3f3c8855 --- /dev/null +++ b/quick-shiro-cas/src/main/java/com/shiro/config/Pac4jConfig.java @@ -0,0 +1,69 @@ +package com.shiro.config; + +import org.pac4j.cas.client.CasClient; +import org.pac4j.cas.config.CasConfiguration; +import org.pac4j.cas.logout.DefaultCasLogoutHandler; +import org.pac4j.core.config.Config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class Pac4jConfig { + + /** + * cas服务地址 + */ + @Value("${cas.server.url}") + private String casServerUrl; + + /** + * 客户端项目地址 + */ + @Value("${cas.project.url}") + private String projectUrl; + + /** 相当于一个标志,可以随意 */ + @Value("${cas.client-name}") + private String clientName; + + /** + * pac4j配置 + * @param casClient + * @return + */ + @Bean + public Config config(CasClient casClient) { + return new Config(casClient); + } + + /** + * cas 客户端配置 + * @param casConfig + * @return + */ + @Bean + public CasClient casClient(CasConfiguration casConfig){ + CasClient casClient = new CasClient(casConfig); + //客户端回调地址 + casClient.setCallbackUrl(projectUrl + "/callback?client_name=" + clientName); + casClient.setName(clientName); + return casClient; + } + + /** + * 请求cas服务端配置 + * @param casLogoutHandler + */ + @Bean + public CasConfiguration casConfig(){ + final CasConfiguration configuration = new CasConfiguration(); + //CAS server登录地址 + configuration.setLoginUrl(casServerUrl + "/login"); + configuration.setAcceptAnyProxy(true); + configuration.setPrefixUrl(casServerUrl + "/"); + configuration.setLogoutHandler(new DefaultCasLogoutHandler<>()); + return configuration; + } + +} \ No newline at end of file diff --git a/quick-shiro-cas/src/main/java/com/shiro/config/ShiroConfig.java b/quick-shiro-cas/src/main/java/com/shiro/config/ShiroConfig.java new file mode 100644 index 00000000..9058543c --- /dev/null +++ b/quick-shiro-cas/src/main/java/com/shiro/config/ShiroConfig.java @@ -0,0 +1,177 @@ +package com.shiro.config; + +import io.buji.pac4j.filter.CallbackFilter; +import io.buji.pac4j.filter.LogoutFilter; +import io.buji.pac4j.filter.SecurityFilter; +import io.buji.pac4j.subject.Pac4jSubjectFactory; +import org.apache.shiro.session.mgt.SessionManager; +import org.apache.shiro.session.mgt.eis.MemorySessionDAO; +import org.apache.shiro.session.mgt.eis.SessionDAO; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.servlet.SimpleCookie; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.pac4j.core.config.Config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.DelegatingFilterProxy; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * + * @author wangxc + * @date: 2022/9/5 21:15 + * + */ +@Configuration +public class ShiroConfig { + + /** + * 客户端项目地址 + */ + @Value("${cas.project.url}") + private String projectUrl; + + /** + * CAS服务器地址 + */ + @Value("${cas.server.url}") + private String casServerUrl; + + /** 客户端名称 */ + @Value("${cas.client-name}") + private String clientName; + + @Bean("securityManager") + public DefaultWebSecurityManager securityManager(Pac4jSubjectFactory subjectFactory, SessionManager sessionManager, + CasRealm casRealm) { + DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); + manager.setRealm(casRealm); + manager.setSubjectFactory(subjectFactory); + manager.setSessionManager(sessionManager); + return manager; + } + + @Bean + public CasRealm casRealm() { + CasRealm realm = new CasRealm(); + // 使用自定义的realm + realm.setClientName(clientName); + realm.setCachingEnabled(false); + //暂时不使用缓存 + realm.setAuthenticationCachingEnabled(false); + realm.setAuthorizationCachingEnabled(false); + return realm; + } + + /** + * 使用 pac4j 的 subjectFactory + * @return + */ + @Bean + public Pac4jSubjectFactory subjectFactory() { + return new Pac4jSubjectFactory(); + } + + @Bean + public FilterRegistrationBean filterRegistrationBean() { + FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); + filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); + filterRegistration.addInitParameter("targetFilterLifecycle", "true"); + filterRegistration.setEnabled(true); + filterRegistration.addUrlPatterns("/*"); + filterRegistration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD); + return filterRegistration; + } + + /** + * 加载shiroFilter权限控制规则 + * @param shiroFilterFactoryBean + */ + private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean) { + Map filterChainDefinitionMap = new LinkedHashMap<>(); + filterChainDefinitionMap.put("/", "securityFilter"); + filterChainDefinitionMap.put("/index", "securityFilter"); + filterChainDefinitionMap.put("/callback", "callbackFilter"); + filterChainDefinitionMap.put("/logout", "logout"); + filterChainDefinitionMap.put("/**", "anon"); + shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + } + + + /** + * shiroFilter + * @param securityManager + * @param config + * @return + */ + @Bean("shiroFilter") + public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, Config config) { + ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); + // 必须设置 SecurityManager + shiroFilterFactoryBean.setSecurityManager(securityManager); + //shiroFilterFactoryBean.setUnauthorizedUrl("/403"); + // 添加casFilter到shiroFilter中 + loadShiroFilterChain(shiroFilterFactoryBean); + Map filters = new HashMap<>(4); + //cas 资源认证拦截器 + SecurityFilter securityFilter = new SecurityFilter(); + securityFilter.setConfig(config); + securityFilter.setClients(clientName); + filters.put("securityFilter", securityFilter); + //cas 认证后回调拦截器 + CallbackFilter callbackFilter = new CallbackFilter(); + callbackFilter.setConfig(config); + callbackFilter.setDefaultUrl(projectUrl); + filters.put("callbackFilter", callbackFilter); +// 注销 拦截器 + LogoutFilter logoutFilter = new LogoutFilter(); + logoutFilter.setConfig(config); + logoutFilter.setCentralLogout(true); + logoutFilter.setLocalLogout(true); + //添加logout后 跳转到指定url url的匹配规则 默认为 /.*; + logoutFilter.setLogoutUrlPattern(".*"); + logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName); + filters.put("logout", logoutFilter); + shiroFilterFactoryBean.setFilters(filters); + return shiroFilterFactoryBean; + } + + @Bean + public SessionDAO sessionDAO() { + return new MemorySessionDAO(); + } + + /** + * 自定义cookie名称 + * @return + */ + @Bean + public SimpleCookie sessionIdCookie() { + SimpleCookie cookie = new SimpleCookie("sid"); + cookie.setMaxAge(-1); + cookie.setPath("/"); + cookie.setHttpOnly(false); + return cookie; + } + + @Bean + public DefaultWebSessionManager sessionManager(SimpleCookie sessionIdCookie, SessionDAO sessionDAO) { + DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); + sessionManager.setSessionIdCookie(sessionIdCookie); + sessionManager.setSessionIdCookieEnabled(true); + //30分钟 + sessionManager.setGlobalSessionTimeout(180000); + sessionManager.setSessionDAO(sessionDAO); + sessionManager.setDeleteInvalidSessions(true); + sessionManager.setSessionValidationSchedulerEnabled(true); + return sessionManager; + } +} diff --git a/quick-shiro-cas/src/main/java/com/shiro/controller/MainController.java b/quick-shiro-cas/src/main/java/com/shiro/controller/MainController.java new file mode 100644 index 00000000..8837b196 --- /dev/null +++ b/quick-shiro-cas/src/main/java/com/shiro/controller/MainController.java @@ -0,0 +1,35 @@ +package com.shiro.controller; + +import com.shiro.service.UserService; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +@Controller +@RequestMapping("/") +public class MainController { + @Autowired + private UserService userService; + + @RequestMapping(value = "", method = RequestMethod.GET) + @ResponseBody + public String index() { + return "index " + userService.getUsername(); + } + + @GetMapping("/callback") + @ResponseBody + public String hello() { + Subject subject = SecurityUtils.getSubject(); + System.out.println("subject = " + subject); + return "hello" + subject.getPrincipal(); + } + + @GetMapping("/logout") + public String logout() { + return "index"; + } + +} diff --git a/quick-shiro-cas/src/main/java/com/shiro/service/UserService.java b/quick-shiro-cas/src/main/java/com/shiro/service/UserService.java new file mode 100644 index 00000000..0ac4de63 --- /dev/null +++ b/quick-shiro-cas/src/main/java/com/shiro/service/UserService.java @@ -0,0 +1,5 @@ +package com.shiro.service; + +public interface UserService { + String getUsername(); +} \ No newline at end of file diff --git a/quick-shiro-cas/src/main/java/com/shiro/service/impl/UserServiceImpl.java b/quick-shiro-cas/src/main/java/com/shiro/service/impl/UserServiceImpl.java new file mode 100644 index 00000000..32f02315 --- /dev/null +++ b/quick-shiro-cas/src/main/java/com/shiro/service/impl/UserServiceImpl.java @@ -0,0 +1,25 @@ +package com.shiro.service.impl; + +import com.shiro.service.UserService; +import io.buji.pac4j.subject.Pac4jPrincipal; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.Subject; +import org.springframework.stereotype.Service; + +@Service("userService") +public class UserServiceImpl implements UserService { + @Override + public String getUsername() { + Subject subject = SecurityUtils.getSubject(); + if (subject == null || subject.getPrincipals() == null) { + return null; + } + PrincipalCollection pcs = subject.getPrincipals(); + Pac4jPrincipal p = pcs.oneByType(Pac4jPrincipal.class); + if (p != null) { + return p.getProfile().getId(); + } + return null; + } +} diff --git a/quick-shiro-cas/src/main/resources/application.properties b/quick-shiro-cas/src/main/resources/application.properties new file mode 100644 index 00000000..e0047055 --- /dev/null +++ b/quick-shiro-cas/src/main/resources/application.properties @@ -0,0 +1,14 @@ +server.port=8080 + +#server.ssl.protocol: TLS +#server.ssl.key-store: classpath:server.keystore +#server.ssl.key-alias: tomcat +#server.ssl.enabled:true +#server.ssl.key-store-password: changeit +#server.ssl.key-store-type: JKS + + + +cas.client-name=app +cas.server.url=https://127.0.0.1:8443/cas +cas.project.url=http://127.0.0.1:8080 \ No newline at end of file diff --git a/quick-shiro-cas/src/main/resources/server.keystore b/quick-shiro-cas/src/main/resources/server.keystore new file mode 100644 index 00000000..87b813af Binary files /dev/null and b/quick-shiro-cas/src/main/resources/server.keystore differ diff --git a/quick-shiro/README.md b/quick-shiro/README.md new file mode 100644 index 00000000..a8ee733f --- /dev/null +++ b/quick-shiro/README.md @@ -0,0 +1,46 @@ +初步完成支持登陆认证和header中的key认证(JWT) +- 实现一个filter、realm和token HeaderFilter、HeaderRealm和HeaderToken +- 通过对请求进行判断,如果header头包含key,则进行key的校验,不包含则进行登陆认证 + + + + +11•授权需要维承 AuthorizingRealm 类,并实现其 doGetAuthorizationInfo方法 +22. AuthorizingRea lm 类继承自 AuthenticatingRealm, 但没有实现 AuthenticatingRea lm 中的 + 3docetAuthenticationInfo, 所以认证和授权只需要继承 AuthorizingRea lm 就可以了.同时实现他的两个抽象; + 4 +51. 为什么使用 MD5 盐值加密: + 62.如何做到: +71) .在docetAuthenticationInfo 方法返回值创建 Simp leAuthenticationInf• 对象的时候,需要使用 + 8 SimpleAuthenticationInfo (principal, credentials, credentialssalt, realmvame)构造司 + 9.2)。使用Bytesource .util.bytes()来计算盐值, +103) . 盐值需要唯一: + 一般使用随机字符串或 user id +114) •使用new SimpleHash (hashAlgorithnName credentials, + salt, hashIterations):来计1 + 12 + 13 1.如何把一个字符串加密为 MD5 + 142.替换当前 Rea lm 的credentialsMatche王 屬性, 直接使用 Hashedcredent ial sMatcher 对象,并设置; + 15 + 16密码的比对: + 17通过 Authent icatingRealm 的credentialsMatcher 属性来进行的密码的比对! + 18 + 191.获取当前的 Subject.调用 Securityutils.getsubject() + 202.测试当前的用户是否己经被认证。即是否己经登录.调用 suboject 的 isAuthenticated () +213. +若没有被认证,则把用户名和密码封装为 Use rname Passwo rdroken对象 +221) .创建一个表单页面 + 232).把请求提交到 SpringMVC 的 Handler + 243).获取用户名和密码。 + 25 A + 执行沯录 + 调田 cihiemtl 的lanin (znthentilrztinnnakenll + 方汁 + + +参考内容: +https://www.w3cschool.cn/shiro/co4m1if2.html +https://juejin.cn/post/6992391181330186270 +https://blog.csdn.net/a23452/article/details/125967279 + +支持多种登录:https://blog.csdn.net/zhourenfei17/article/details/88826911 \ No newline at end of file diff --git a/quick-shiro/pom.xml b/quick-shiro/pom.xml new file mode 100644 index 00000000..14bd0031 --- /dev/null +++ b/quick-shiro/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + quick-shiro + 1.0-SNAPSHOT + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + quick-shiro + + + UTF-8 + 1.7 + 1.7 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.apache.shiro + shiro-web + + + + org.apache.shiro + shiro-ehcache + + + + org.apache.shiro + shiro-spring + + + + org.apache.shiro + shiro-cas + + + + org.projectlombok + lombok + + + + junit + junit + test + + + + \ No newline at end of file diff --git a/quick-shiro/src/main/java/com/shiro/quick/ShiroApplication.java b/quick-shiro/src/main/java/com/shiro/quick/ShiroApplication.java new file mode 100644 index 00000000..a3572177 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/ShiroApplication.java @@ -0,0 +1,14 @@ +package com.shiro.quick; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Hello world! + */ +@SpringBootApplication +public class ShiroApplication { + public static void main(String[] args) { + SpringApplication.run(ShiroApplication.class); + } +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/TestMain.java b/quick-shiro/src/main/java/com/shiro/quick/TestMain.java new file mode 100644 index 00000000..4fd6720a --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/TestMain.java @@ -0,0 +1,17 @@ +package com.shiro.quick; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TestMain { + + + + public static void main(String[] args) { + List collect = Stream.of(1, 23, 45, 546, 456, 54, 7, 56, 45, 6, 43645).filter(item -> item > 1000).collect(Collectors.toList()); + collect.forEach(System.out::println); + } +} + + diff --git a/quick-shiro/src/main/java/com/shiro/quick/config/ShiroConfig.java b/quick-shiro/src/main/java/com/shiro/quick/config/ShiroConfig.java new file mode 100644 index 00000000..66eb44fd --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/config/ShiroConfig.java @@ -0,0 +1,192 @@ +package com.shiro.quick.config; + +import com.shiro.quick.shiro.CustomSessionManager; +import com.shiro.quick.shiro.filter.HeaderFilter; +import com.shiro.quick.shiro.realm.HeaderRealm; +import com.shiro.quick.shiro.realm.MyRealm; +import com.shiro.quick.shiro.realm.OtherRealm; +import org.apache.shiro.authc.credential.HashedCredentialsMatcher; +import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy; +import org.apache.shiro.authc.pam.ModularRealmAuthenticator; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.session.mgt.SessionManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.filter.mgt.DefaultFilter; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.servlet.SimpleCookie; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; + + +/** + * SessionManager 暂未集成 + */ +@Configuration +public class ShiroConfig { + + + /** + * 自定义Realm,指定密码加密算法、加密次数等等 + * + * @return + */ + @Bean + public MyRealm myRealm() { + MyRealm myRealm = new MyRealm(); + HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); + hashedCredentialsMatcher.setHashAlgorithmName("MD5"); + hashedCredentialsMatcher.setHashIterations(1); + myRealm.setCredentialsMatcher(hashedCredentialsMatcher); + return myRealm; + } + + /** + * 头部指定key认证 + * @return + */ + @Bean + public HeaderRealm headerRealm() { + return new HeaderRealm(); + } + + /** + * 其他认证 + * @return + */ + @Bean + public OtherRealm otherRealm() { + OtherRealm otherRealm = new OtherRealm(); + HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); + hashedCredentialsMatcher.setHashAlgorithmName("SHA1"); + hashedCredentialsMatcher.setHashIterations(1); + otherRealm.setCredentialsMatcher(hashedCredentialsMatcher); + return otherRealm; + } + + + @Bean + public ModularRealmAuthenticator authenticator() { + ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator(); + LinkedList realmLinkedList = new LinkedList<>(); + realmLinkedList.add(myRealm()); + realmLinkedList.add(otherRealm()); + realmLinkedList.add(new HeaderRealm()); + + modularRealmAuthenticator.setRealms(realmLinkedList); + /** + * 认证通过策略 + * + * 所有领域、仅 1 个或多个领域、没有领域等都成功 + */ +// modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); + modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); + + + return modularRealmAuthenticator; + } + + + @Bean("sessionManager") + public SessionManager sessionManager() { + DefaultWebSessionManager sm = new CustomSessionManager(); + sm.setSessionValidationInterval(10800000L); + sm.setGlobalSessionTimeout(10800000L); + sm.setSessionValidationSchedulerEnabled(Boolean.FALSE); + sm.setSessionIdUrlRewritingEnabled(Boolean.FALSE); + sm.setSessionIdCookieEnabled(true); + sm.setSessionIdCookie(sessionIdCookie()); + return sm; + } + + @Bean + public SecurityManager securityManager(SessionManager sessionManager,ModularRealmAuthenticator authenticator) { + DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); + defaultSecurityManager.setAuthenticator(authenticator); + defaultSecurityManager.setSessionManager(sessionManager); +// defaultSecurityManager.setSubjectFactory(new HeaderDefaultSubjectFactory()); + + return defaultSecurityManager; + } + + public SimpleCookie sessionIdCookie(){ + //这个参数是cookie的名称 + SimpleCookie simpleCookie = new SimpleCookie("JSESSIONID"); + simpleCookie.setHttpOnly(true); + simpleCookie.setSecure(true); + //maxAge=-1表示浏览器关闭时失效此Cookie + simpleCookie.setMaxAge(10800000); + return simpleCookie; + } + + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { + ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); + + /** + * 添加jwt过滤器,并在下面注册 + * 也就是将jwtFilter注册到shiro的Filter中 + * 指定除了login和logout之外的请求都先经过jwtFilter + */ + Map filterMap = new HashMap<>(); + //这个地方其实另外两个filter可以不设置,默认就是 + filterMap.put("hf", new HeaderFilter()); + factoryBean.setFilters(filterMap); + + factoryBean.setSecurityManager(securityManager); + factoryBean.setLoginUrl("/login"); + factoryBean.setSuccessUrl("/index"); + factoryBean.setUnauthorizedUrl("/unauthorized"); + + Map map = new LinkedHashMap<>(); + /** + * {@link DefaultFilter} + */ + map.put("/doLogin", "anon"); + map.put("/getLogin/**", "anon"); + map.put("/anno/hello1", "anon"); + map.put("/vip", "roles[admin]"); + map.put("/common", "roles[user]"); + // 登陆鉴权 + map.put("/**", "hf,authc"); + // header 鉴权 + factoryBean.setFilterChainDefinitionMap(map); + return factoryBean; + } + + + /** + * 一下三个bean是为了让@RequiresRoles({"admin"}) 生效 + * 这两个是 Shiro 的注解,我们需要借助 SpringAOP 扫描到它们 + * + * @return + */ + @Bean + public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + @Bean + public static DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { + DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + advisorAutoProxyCreator.setProxyTargetClass(true); + return advisorAutoProxyCreator; + } + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); + return authorizationAttributeSourceAdvisor; + } +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/config/SpringBeanFactory.java b/quick-shiro/src/main/java/com/shiro/quick/config/SpringBeanFactory.java new file mode 100644 index 00000000..c24e49bc --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/config/SpringBeanFactory.java @@ -0,0 +1,28 @@ +package com.shiro.quick.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.stereotype.Component; + +@Component +public class SpringBeanFactory implements BeanFactoryAware { + + private static BeanFactory beanFactory; + + public static Object getBean(String beanName) { + return beanFactory.getBean(beanName); + } + + public static T getBean(Class clazs) { + return beanFactory.getBean(clazs); + } + + public static T getBean(String beanName, Class clazs) { + return clazs.cast(getBean(beanName)); + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + SpringBeanFactory.beanFactory = beanFactory; + } +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/config/WebConfig.java b/quick-shiro/src/main/java/com/shiro/quick/config/WebConfig.java new file mode 100644 index 00000000..a4f276fe --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/config/WebConfig.java @@ -0,0 +1,16 @@ +package com.shiro.quick.config; + +import com.shiro.quick.intecepter.AccessInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +//@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + + registry.addInterceptor(new AccessInterceptor()); + } +} \ No newline at end of file diff --git a/quick-shiro/src/main/java/com/shiro/quick/controller/AnnoController.java b/quick-shiro/src/main/java/com/shiro/quick/controller/AnnoController.java new file mode 100644 index 00000000..ec85c6c6 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/controller/AnnoController.java @@ -0,0 +1,13 @@ +package com.shiro.quick.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class AnnoController { + + @GetMapping("/anno/hello1") + public String hello() { + return "anno hello"; + } +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/controller/LoginController.java b/quick-shiro/src/main/java/com/shiro/quick/controller/LoginController.java new file mode 100644 index 00000000..f8d25d7f --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/controller/LoginController.java @@ -0,0 +1,85 @@ +package com.shiro.quick.controller; + +import com.shiro.quick.service.TestService; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.Subject; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + + +@Controller +public class LoginController { + + @Resource + private TestService testService; + + + @GetMapping("/getLogin/{username}/{password}") + public String doLoginGet(@PathVariable("username") String username,@PathVariable("password") String password) { + Subject subject = SecurityUtils.getSubject(); + try { + subject.login(new UsernamePasswordToken(username, password)); + System.out.println("登录成功!"); + return "redirect:/getIndex.html"; + } catch (AuthenticationException e) { + e.printStackTrace(); + System.out.println("登录失败!"); + } + return "redirect:/login"; + } + + @PostMapping("/doLogin") + public String doLogin(String username, String password) { + Subject subject = SecurityUtils.getSubject(); + try { + subject.login(new UsernamePasswordToken(username, password)); + System.out.println("登录成功!"); + return "redirect:/index"; + } catch (AuthenticationException e) { + e.printStackTrace(); + System.out.println("登录失败!"); + } + return "redirect:/login"; + } + + @GetMapping("/hello") + @ResponseBody + public String hello() { + return "hello"; + } + + @GetMapping("/index") + @ResponseBody + public String index() { + String wel = "hello ~ "; +// Subject subject = SecurityUtils.getSubject(); +// System.out.println(subject.hasRole("admin")); +// String s = testService.vipPrint(); +// wel = wel + s; + return wel; + } + + @GetMapping("/login") + @ResponseBody + public String login() { + return "please login!"; + } + + + @GetMapping("/vip") + @ResponseBody + public String vip() { + return "hello vip"; + } + + + @GetMapping("/common") + @ResponseBody + public String common() { + return "hello common"; + } +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/intecepter/AccessInterceptor.java b/quick-shiro/src/main/java/com/shiro/quick/intecepter/AccessInterceptor.java new file mode 100644 index 00000000..3e64618a --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/intecepter/AccessInterceptor.java @@ -0,0 +1,18 @@ +package com.shiro.quick.intecepter; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Slf4j +public class AccessInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + log.info("AccessInterceptor"); +// return super.preHandle(request, response, handler); + return true; + } +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/service/TestService.java b/quick-shiro/src/main/java/com/shiro/quick/service/TestService.java new file mode 100644 index 00000000..ca45dfd2 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/service/TestService.java @@ -0,0 +1,15 @@ +package com.shiro.quick.service; + +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.stereotype.Service; + +@Service +public class TestService { + + + @RequiresRoles({"admin"}) + public String vipPrint() { + return "i am vip"; + } + +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/CustomSessionManager.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/CustomSessionManager.java new file mode 100644 index 00000000..c01f6977 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/CustomSessionManager.java @@ -0,0 +1,306 @@ +package com.shiro.quick.shiro; + +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.session.ExpiredSessionException; +import org.apache.shiro.session.InvalidSessionException; +import org.apache.shiro.session.Session; +import org.apache.shiro.session.mgt.SessionContext; +import org.apache.shiro.session.mgt.SessionKey; +import org.apache.shiro.web.servlet.Cookie; +import org.apache.shiro.web.servlet.ShiroHttpServletRequest; +import org.apache.shiro.web.servlet.ShiroHttpSession; +import org.apache.shiro.web.servlet.SimpleCookie; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.apache.shiro.web.util.WebUtils; +import org.springframework.util.StringUtils; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.Serializable; + +import static com.shiro.quick.shiro.filter.HeaderFilter.HEADER_KEY; + +/** + * https://william-zzw.github.io/2018/07/13/Shiro%E6%98%AF%E5%A6%82%E4%BD%95%E6%8B%A6%E6%88%AA%E6%9C%AA%E7%99%BB%E5%BD%95%E8%AF%B7%E6%B1%82%E7%9A%84(%E4%BA%8C)/ + * 自定义的sessionManager + */ +@Slf4j +public class CustomSessionManager extends DefaultWebSessionManager { +// private static final String AUTH_TOKEN = "Token"; +// private static final String DEVICE = "device"; + private static final String LOGIN_STATE_ID = "JSESSIONID"; + private static final String MOBILE = "mobile"; + + public CustomSessionManager() { + super(); + } + + /** + * 重写父类获取sessionID的方法,若请求为APP或者H5则从请求头中取出token + * + * @param request 请求参数 + * @param response 响应参数 + * @return id + */ + @Override + protected Serializable getSessionId(ServletRequest request, ServletResponse response) { + if (!(request instanceof HttpServletRequest)) { + log.debug("Current request is not an HttpServletRequest - cannot get session ID. Returning null."); + return null; + } + HttpServletRequest httpRequest = WebUtils.toHttp(request); + if (StringUtils.hasText(httpRequest.getHeader(HEADER_KEY))) { + String token = httpRequest.getHeader(HEADER_KEY); +// if (!StringUtils.hasText(httpRequest.getHeader(DEVICE)) || !MOBILE.equals(httpRequest.getHeader(DEVICE))) { +// token = getLoginStateId(token); +// } + // 每次读取之后都把当前的token放入response中 + HttpServletResponse httpResponse = WebUtils.toHttp(response); + if (StringUtils.hasText(token)) { + httpResponse.setHeader(HEADER_KEY, token); + request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header"); + request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token); + request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); + } + //sessionIdUrlRewritingEnabled的配置为false,不会在url的后面带上sessionID + request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled()); + return token; + } + return getReferencedSessionId(request, response); + } + +// private String getLoginStateId(String token) { +// try { +// DecodedJWT jwt = JWT.decode(token); +// return jwt.getClaim(LOGIN_STATE_ID).asString(); +// } catch (JWTDecodeException e) { +// return null; +// } +// } + + /** + * shiro默认从cookie中获取sessionId + * + * @param request 请求参数 + * @param response 响应参数 + * @return + */ + private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) { + String id = getSessionIdCookieValue(request, response); + if (id != null) { + request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, + ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE); + } else { + //not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting): + //try the URI path segment parameters first: + id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME); + if (id == null) { + //not a URI path segment parameter, try the query parameters: + String name = getSessionIdName(); + id = request.getParameter(name); + if (id == null) { + //try lowercase: + id = request.getParameter(name.toLowerCase()); + } + } + if (id != null) { + request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, + ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); + } + } + if (id != null) { + request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); + //automatically mark it valid here. If it is invalid, the + //onUnknownSession method below will be invoked and we'll remove the attribute at that time. + request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); + } + // always set rewrite flag - SHIRO-361 + request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled()); + return id; + } + + /** + * copy from DefaultWebSessionManager + * + * @param request 请求参数 + * @param response 响应参数 + * @return + */ + private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) { + if (!isSessionIdCookieEnabled()) { + log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie."); + return null; + } + if (!(request instanceof HttpServletRequest)) { + log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie. Returning null."); + return null; + } + HttpServletRequest httpRequest = (HttpServletRequest) request; + return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response)); + } + + private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) { + if (!(servletRequest instanceof HttpServletRequest)) { + return null; + } + HttpServletRequest request = (HttpServletRequest) servletRequest; + String uri = request.getRequestURI(); + if (uri == null) { + return null; + } + int queryStartIndex = uri.indexOf('?'); + if (queryStartIndex >= 0) { + uri = uri.substring(0, queryStartIndex); + } + int index = uri.indexOf(';'); + if (index < 0) { + //no path segment params - return: + return null; + } + //there are path segment params, let's get the last one that may exist: + final String TOKEN = paramName + "="; + uri = uri.substring(index + 1); + //we only care about the last JSESSIONID param: + index = uri.lastIndexOf(TOKEN); + if (index < 0) { + //no segment param: + return null; + } + uri = uri.substring(index + TOKEN.length()); + index = uri.indexOf(';'); + if (index >= 0) { + uri = uri.substring(0, index); + } + return uri; + } + + private String getSessionIdName() { + String name = this.getSessionIdCookie() != null ? this.getSessionIdCookie().getName() : null; + if (name == null) { + name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME; + } + return name; + } + + + /** + * 存储会话id到response header中 + * + * @param currentId 会话ID + * @param request HttpServletRequest + * @param response HttpServletResponse + */ + private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) { + if (currentId == null) { + String msg = "sessionId cannot be null when persisting for subsequent requests."; + throw new IllegalArgumentException(msg); + } + String idString = currentId.toString(); + //增加判断,如果请求头中包含DEVICE=MOBILE,则将sessionId放在header中返回 + if (StringUtils.hasText(request.getHeader(HEADER_KEY))) { + response.setHeader(HEADER_KEY, idString); + } else { + Cookie template = getSessionIdCookie(); + Cookie cookie = new SimpleCookie(template); + cookie.setValue(idString); + cookie.saveTo(request, response); + } + log.trace("Set session ID cookie for session with id {}", idString); + } + + /** + * 设置deleteMe到response header中 + * + * @param request request + * @param response HttpServletResponse + */ + private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) { + if (StringUtils.hasText(request.getHeader(HEADER_KEY))) { + response.setHeader(HEADER_KEY, Cookie.DELETED_COOKIE_VALUE); + } else { + getSessionIdCookie().removeFrom(request, response); + } + } + + /** + * 会话创建 + * Stores the Session's ID, usually as a Cookie, to associate with future requests. + * + * @param session the session that was just {@link #createSession created}. + */ + @Override + protected void onStart(Session session, SessionContext context) { + super.onStart(session, context); + if (!WebUtils.isHttp(context)) { + log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " + + "pair. No session ID cookie will be set."); + return; + } + HttpServletRequest request = WebUtils.getHttpRequest(context); + HttpServletResponse response = WebUtils.getHttpResponse(context); + if (isSessionIdCookieEnabled()) { + Serializable sessionId = session.getId(); + storeSessionId(sessionId, request, response); + } else { + log.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId()); + } + request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE); + request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE); + } + + /** + * 会话失效 + * + * @param s Session + * @param ese ExpiredSessionException + * @param key SessionKey + */ + @Override + protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) { + super.onExpiration(s, ese, key); + onInvalidation(key); + } + + @Override + protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) { + super.onInvalidation(session, ise, key); + onInvalidation(key); + } + + private void onInvalidation(SessionKey key) { + ServletRequest request = WebUtils.getRequest(key); + if (request != null) { + request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID); + } + if (WebUtils.isHttp(key)) { + log.debug("Referenced session was invalid. Removing session ID cookie."); + removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key)); + } else { + log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " + + "pair. Session ID cookie will not be removed due to invalidated session."); + } + } + + /** + * 会话销毁 + * + * @param session Session + * @param key SessionKey + */ + @Override + protected void onStop(Session session, SessionKey key) { + super.onStop(session, key); + if (WebUtils.isHttp(key)) { + HttpServletRequest request = WebUtils.getHttpRequest(key); + HttpServletResponse response = WebUtils.getHttpResponse(key); + log.debug("Session has been stopped (subject logout or explicit stop). Removing session ID cookie."); + removeSessionIdCookie(request, response); + } else { + log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " + + "pair. Session ID cookie will not be removed due to stopped session."); + } + } +} + diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/HeaderDefaultSubjectFactory.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/HeaderDefaultSubjectFactory.java new file mode 100644 index 00000000..2d63cd07 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/HeaderDefaultSubjectFactory.java @@ -0,0 +1,16 @@ +package com.shiro.quick.shiro; + +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.SubjectContext; +import org.apache.shiro.web.mgt.DefaultWebSubjectFactory; + + +public class HeaderDefaultSubjectFactory extends DefaultWebSubjectFactory { + + @Override + public Subject createSubject(SubjectContext context) { + // 不创建 session + context.setSessionCreationEnabled(false); + return super.createSubject(context); + } +} \ No newline at end of file diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/RedisSessionDAO.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/RedisSessionDAO.java new file mode 100644 index 00000000..b910c299 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/RedisSessionDAO.java @@ -0,0 +1,67 @@ +//package com.shiro.quick.shiro; +// +// +//import lombok.extern.slf4j.Slf4j; +//import org.apache.shiro.session.Session; +//import org.apache.shiro.session.UnknownSessionException; +//import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; +//import org.springframework.data.redis.core.StringRedisTemplate; +//import org.springframework.web.client.RestTemplate; +// +//import javax.annotation.Resource; +//import java.io.Serializable; +//import java.util.Collection; +//import java.util.Collections; +// +//@Slf4j +//public class RedisSessionDAO extends AbstractSessionDAO { +// private static final String PREFIX_SESSION = "SHIRO:SESSION:"; +// +// +// @Resource +// private StringRedisTemplate redisService; +// +// public void setRedisService(StringRedisTemplate redisService){ +// this.redisService=redisService; +// } +// +// @Override +// protected Serializable doCreate(Session session) { +// Serializable sessionId = generateSessionId(session); +// assignSessionId(session, sessionId); +// update(session); +// return sessionId; +// } +// +// @Override +// protected Session doReadSession(Serializable sessionId) { +// try{ +// return (Session) redisService.get(getSessionKey(sessionId)); +// }catch (Exception e){ +// log.error(this.getClass().getName()+" doReadSession 出现异常"+e.getMessage()); +// return null; +// } +// } +// +// @Override +// public void update(Session session) throws UnknownSessionException { +// long timeout = session.getTimeout(); +// redisService.set(getSessionKey(session.getId()), session, timeout); +// } +// +// @Override +// public void delete(Session session) { +// redisService.del(getSessionKey(session.getId())); +// } +// +// @Override +// public Collection getActiveSessions() { +// return Collections.emptyList(); +// } +// +// public static String getSessionKey(Serializable id) { +// return PREFIX_SESSION + id; +// } +//} +// +// diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/filter/HeaderFilter.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/filter/HeaderFilter.java new file mode 100644 index 00000000..efe73ce7 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/filter/HeaderFilter.java @@ -0,0 +1,92 @@ +package com.shiro.quick.shiro.filter; + +import com.shiro.quick.shiro.token.HeaderToken; +import com.shiro.quick.shiro.utils.ShiroUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.web.filter.AccessControlFilter; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +@Slf4j +public class HeaderFilter extends AccessControlFilter { + + public static final String HEADER_KEY = "Authorization"; + + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { + log.warn("HeaderFilter isAccessAllowed 方法被调用"); + if (StringUtils.isEmpty(ShiroUtil.getHeaderKey(request))) { + return true; + } + + //这里先让它始终返回false来使用onAccessDenied()方法 + return false; + } + + /** + * 返回结果为true表明登录通过 + * + * @param servletRequest the incoming ServletRequest + * @param response the outgoing ServletResponse + * @return + * @throws Exception + */ + @Override + protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse response) throws Exception { + log.warn("HeaderFilter onAccessDenied 方法被调用"); + + //这个地方和前端约定,要求前端将jwtToken放在请求的Header部分 + + //所以以后发起请求的时候就需要在Header中放一个Authorization,值就是对应的Token + String headerKey = ShiroUtil.getHeaderKey(servletRequest); + if (StringUtils.isEmpty(headerKey)) { + saveRequestAndRedirectToLogin(servletRequest, response); + return false; + } + log.info("请求的 Header 中藏有 headerKey {}", headerKey); + HeaderToken token = new HeaderToken(headerKey); + /* + * 下面就是固定写法 + * */ + try { + // 委托 realm 进行登录认证 + //所以这个地方最终还是调用JwtRealm进行的认证 + getSubject(servletRequest, response).login(token); + //也就是subject.login(token) + /** + * 去除session 但不知道是不是正规做法 TODO + * 解决使用header认证之后返回的cookies进行非header认证 + */ + HttpServletResponse servletResponse = (HttpServletResponse) response; + Cookie cookie = new Cookie("JSESSIONID", ""); + servletResponse.addCookie(cookie); + + + } catch (Exception e) { + e.printStackTrace(); + onLoginFail(response); + //调用下面的方法向客户端返回错误信息 + return false; + } + + return true; + //执行方法中没有抛出异常就表示登录成功 + } + + //登录失败时默认返回 401 状态码 + private void onLoginFail(ServletResponse response) throws IOException { + response.setContentType("application/json; charset=utf-8"); + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpResponse.getWriter().write("auth error"); + } + +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/CustomModularRealmAuthenticator.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/CustomModularRealmAuthenticator.java new file mode 100644 index 00000000..1611fc31 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/CustomModularRealmAuthenticator.java @@ -0,0 +1,35 @@ +package com.shiro.quick.shiro.realm; + +import com.shiro.quick.config.SpringBeanFactory; +import com.shiro.quick.shiro.token.HeaderToken; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.pam.ModularRealmAuthenticator; +import org.apache.shiro.realm.Realm; + +import java.util.Collection; + +@Slf4j +public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator { + + /** + * 重写多realm认证 + * + * @param realms 领域 + * @param token 令牌 + * @return {@link AuthenticationInfo} + */ + @Override + protected AuthenticationInfo doMultiRealmAuthentication(Collection realms, AuthenticationToken token) { + Realm finalRealm; + // 匹配Realm名称 + if (token instanceof HeaderToken) { + finalRealm = SpringBeanFactory.getBean(HeaderRealm.class); + } else { + finalRealm = SpringBeanFactory.getBean(MyRealm.class); + } + return super.doSingleRealmAuthentication(finalRealm, token); + } + +} \ No newline at end of file diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/HeaderRealm.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/HeaderRealm.java new file mode 100644 index 00000000..5266c0e9 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/HeaderRealm.java @@ -0,0 +1,54 @@ +package com.shiro.quick.shiro.realm; + +import com.shiro.quick.shiro.token.HeaderToken; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; + +@Slf4j +public class HeaderRealm extends AuthorizingRealm { + /* + * 多重写一个support + * 标识这个Realm是专门用来验证JwtToken + * 不负责验证其他的token(UsernamePasswordToken) + * */ + @Override + public boolean supports(AuthenticationToken token) { + //这个token就是从过滤器中传入的jwtToken + return token instanceof HeaderToken; + } + + //授权 + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + return null; + } + + //认证 + //这个token就是从过滤器中传入的jwtToken + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + + String headerKey = (String) token.getPrincipal(); + if (headerKey == null) { + throw new NullPointerException("headerKey 不允许为空"); + } + if (!"123".equals(headerKey)) { + throw new AuthenticationException("认证失败"); + } + // 校验逻辑 + //... + //下面是验证这个user是否是真实存在的 + log.info("使用header认证: " + headerKey); + + return new SimpleAuthenticationInfo(headerKey, headerKey, "HeaderRealm"); + //这里返回的是类似账号密码的东西,但是jwtToken都是jwt字符串。还需要一个该Realm的类名 + + } + +} \ No newline at end of file diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/MyRealm.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/MyRealm.java new file mode 100644 index 00000000..e1051cc3 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/MyRealm.java @@ -0,0 +1,73 @@ +package com.shiro.quick.shiro.realm; + +import com.shiro.quick.config.ShiroConfig; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.*; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.crypto.hash.SimpleHash; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; + +import java.util.HashSet; +import java.util.Set; + +@Slf4j +public class MyRealm extends AuthorizingRealm { + + + + + // 授权 + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { + + Object principal = principalCollection.getPrimaryPrincipal(); + + Set roles = new HashSet<>(); + roles.add("user"); + if ("admin".equals(principal)) { + roles.add("admin"); + } + + return new SimpleAuthorizationInfo(roles); + } + + + // 认证 + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + log.info("MyRealm doGetAuthenticationInfo"); + + UsernamePasswordToken userToken = (UsernamePasswordToken) token; + String username = userToken.getUsername(); + if ("unknown".equals(username)) { + throw new UnknownAccountException("用户不存在"); + } + + if ("monster".equals(username)) { + throw new LockedAccountException("用户被锁定"); + } + + Object principal = username; + Object credentials = "e10adc3949ba59abbe56e057f20f883e"; + String realmName = getName(); + + + log.info("doGetAuthenticationInfo username: {}", username); + /** + * 见 {@link ShiroConfig#myRealm} 的密码指定算法 + */ + return new SimpleAuthenticationInfo(principal, credentials, realmName); + } + + + public static void main(String[] args) { + String hashAlgorithmName = "MD5"; + String credentials = "123456"; + Object salt = null; + int hashIterations = 100; + SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); + System.out.println(simpleHash); + } +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/OtherRealm.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/OtherRealm.java new file mode 100644 index 00000000..7c3728e5 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/realm/OtherRealm.java @@ -0,0 +1,44 @@ +package com.shiro.quick.shiro.realm; + +import com.shiro.quick.config.ShiroConfig; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.*; +import org.apache.shiro.crypto.hash.SimpleHash; +import org.apache.shiro.realm.AuthenticatingRealm; + +@Slf4j +public class OtherRealm extends AuthenticatingRealm { + + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + log.info("OtherRealm doGetAuthenticationInfo"); + UsernamePasswordToken userToken = (UsernamePasswordToken) token; + String username = userToken.getUsername(); + if ("unknown".equals(username)) { + throw new UnknownAccountException("用户不存在"); + } + + if ("monster".equals(username)) { + throw new LockedAccountException("用户被锁定"); + } + + Object principal = username; + Object credentials = "7c4a8d09ca3762af61e59520943dc26494f8941b"; + String realmName = getName(); + + log.info("doGetAuthenticationInfo username: {}", username); + /** + * 见 {@link ShiroConfig#myRealm} 的密码指定算法 + */ + return new SimpleAuthenticationInfo(principal, credentials, realmName); + } + + public static void main(String[] args) { + String hashAlgorithmName = "SHA1"; + String credentials = "123456"; + Object salt = null; + int hashIterations = 1; + SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); + System.out.println(simpleHash); + } +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/token/HeaderToken.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/token/HeaderToken.java new file mode 100644 index 00000000..02581f81 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/token/HeaderToken.java @@ -0,0 +1,22 @@ +package com.shiro.quick.shiro.token; + +import org.apache.shiro.authc.AuthenticationToken; + +public class HeaderToken implements AuthenticationToken { + + private String key; + + public HeaderToken(String key) { + this.key = key; + } + + @Override + public Object getPrincipal() { + return key; + } + + @Override + public Object getCredentials() { + return key; + } +} diff --git a/quick-shiro/src/main/java/com/shiro/quick/shiro/utils/ShiroUtil.java b/quick-shiro/src/main/java/com/shiro/quick/shiro/utils/ShiroUtil.java new file mode 100644 index 00000000..4c154ab8 --- /dev/null +++ b/quick-shiro/src/main/java/com/shiro/quick/shiro/utils/ShiroUtil.java @@ -0,0 +1,13 @@ +package com.shiro.quick.shiro.utils; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +import static com.shiro.quick.shiro.filter.HeaderFilter.HEADER_KEY; + +public class ShiroUtil { + public static String getHeaderKey(ServletRequest servletRequest) { + HttpServletRequest request = (HttpServletRequest) servletRequest; + return request.getHeader(HEADER_KEY); + } +} diff --git a/quick-shiro/src/main/resources/static/getIndex.html b/quick-shiro/src/main/resources/static/getIndex.html new file mode 100644 index 00000000..6becc27b --- /dev/null +++ b/quick-shiro/src/main/resources/static/getIndex.html @@ -0,0 +1,3 @@ + +

hello

+ \ No newline at end of file diff --git a/quick-shiro/src/test/java/com/quick/AppTest.java b/quick-shiro/src/test/java/com/quick/AppTest.java new file mode 100644 index 00000000..7a1064c0 --- /dev/null +++ b/quick-shiro/src/test/java/com/quick/AppTest.java @@ -0,0 +1,20 @@ +package com.quick; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +} diff --git a/quick-simhash/src/main/java/com/quick/simhash/ICTCLAS50.java b/quick-simhash/src/main/java/com/quick/simhash/ICTCLAS50.java deleted file mode 100644 index 74d6fe6f..00000000 --- a/quick-simhash/src/main/java/com/quick/simhash/ICTCLAS50.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.quick.simhash; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.util.StringTokenizer; - -public class ICTCLAS50 -{ - static { - try { - String libpath = System.getProperty("java.library.path"); - String path = null; - StringTokenizer st = new StringTokenizer(libpath, System.getProperty("path.separator")); - if ( st.hasMoreElements() ) { - path = st.nextToken(); - } - - // copy all dll files to java lib path - File dllFile = null; - InputStream inputStream = null; - FileOutputStream outputStream = null; - byte[] array = null; - - dllFile = new File(new File(path), "ICTCLAS50.dll"); - if (!dllFile.exists()) { - System.out.println("222" + path); - inputStream = ICTCLAS50.class.getResource("/lib/ICTCLAS50.dll").openStream(); - outputStream = new FileOutputStream(dllFile); - array = new byte[1024]; - for (int i = inputStream.read(array); i != -1; i = inputStream.read(array)) { - outputStream.write(array, 0, i); - } - outputStream.close(); - } - - } catch (Exception e) { - e.printStackTrace(); - } - - try { - // load JniCall.dll - System.loadLibrary("ICTCLAS50"); - } catch (Error e) { - e.printStackTrace(); - } - } - //public enum eCodeType - //¡¢ - // CODE_TYPE_UNKNOWN,//type unknown - // CODE_TYPE_ASCII,//ASCII - // CODE_TYPE_GB,//GB2312,GBK,GB10380 - // CODE_TYPE_UTF8,//UTF-8 - // CODE_TYPE_BIG5//BIG5 - //} - - public native boolean ICTCLAS_Init(byte[] sPath); - public native boolean ICTCLAS_Exit(); - public native int ICTCLAS_ImportUserDictFile(byte[] sPath,int eCodeType); - public native int ICTCLAS_SaveTheUsrDic(); - public native int ICTCLAS_SetPOSmap(int nPOSmap); - public native boolean ICTCLAS_FileProcess(byte[] sSrcFilename, int eCodeType, int bPOSTagged,byte[] sDestFilename); - public native byte[] ICTCLAS_ParagraphProcess(byte[] sSrc, int eCodeType, int bPOSTagged); - public native byte[] nativeProcAPara(byte[] sSrc, int eCodeType, int bPOStagged); - /* Use static intializer */ -// static -// { -// String libpath = System.getProperty("java.library.path"); -//// //String dir = "D:\\myfc\\javaAPI\\ICTCLAS50.dll"; -////// System.loadLibrary("ICTCLAS50"); -// System.load(libpath+File.separator+"ICTCLAS50.dll"); -// -// } -} \ No newline at end of file diff --git a/quick-simhash/src/main/java/com/quick/simhash/News.java b/quick-simhash/src/main/java/com/quick/simhash/News.java deleted file mode 100644 index 76ccf4d4..00000000 --- a/quick-simhash/src/main/java/com/quick/simhash/News.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.quick.simhash; - -import java.util.HashMap; -import java.util.Map; - -public class News { - // 新闻具体内容 - String content; - // 新闻包含的词的个数统计值 - HashMap word2Count; - - public News(String content) { - this.content = content; - this.word2Count = new HashMap(); - } - - /** - * 将分词后的字符串进行关键词词数统计 - */ - public void statWords() { - int index; - int invalidCount; - double count; - // 词频 - double wordRate; - String w; - String[] array; - - invalidCount = 0; - array = this.content.split(" "); - for (String str : array) { - index = str.indexOf('/'); - if (index == -1) { - continue; - } - w = str.substring(0, index); - - // 只过滤掉标点符/wn,逗号, - if (str.contains("wn") || str.contains("wd")) { - invalidCount++; - continue; - } - - count = 0; - if (this.word2Count.containsKey(w)) { - count = this.word2Count.get(w); - } - - // 做计数的更新 - count++; - this.word2Count.put(w, count); - } - - // 进行总词语的记录汇总 - for (Map.Entry entry : this.word2Count.entrySet()) { - w = entry.getKey(); - count = entry.getValue(); - - wordRate = 1.0 * count / (array.length - invalidCount); - this.word2Count.put(w, wordRate); - } - } - - /** - * 根据词语名称获取词频 - * - * @param word - * 词的名称 - * - */ - public double getWordFrequentValue(String word) { - if(this.word2Count.containsKey(word)){ - return this.word2Count.get(word); - }else{ - return -1; - } - } -} \ No newline at end of file diff --git a/quick-simhash/src/main/java/com/quick/simhash/SimHash.java b/quick-simhash/src/main/java/com/quick/simhash/SimHash.java deleted file mode 100644 index 40e55828..00000000 --- a/quick-simhash/src/main/java/com/quick/simhash/SimHash.java +++ /dev/null @@ -1,208 +0,0 @@ -package com.quick.simhash; /** - * Function: simHash 判断文本相似度,该示例程支持中文
- * @author: wgji - * @date:2014年4月9日 下午3:18:36 - * @comment: - */ -import org.wltea.analyzer.IKSegmentation; -import org.wltea.analyzer.Lexeme; - -import java.io.IOException; -import java.io.StringReader; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class SimHash { - - private String tokens; - - private BigInteger intSimHash; - - private String strSimHash; - - private int hashbits = 64; - - public SimHash(String tokens) throws IOException { - this.tokens = tokens; - this.intSimHash = this.simHash(); - } - - public SimHash(String tokens, int hashbits) throws IOException { - this.tokens = tokens; - this.hashbits = hashbits; - this.intSimHash = this.simHash(); - } - - Map wordMap = new HashMap(); - - public BigInteger simHash() throws IOException { - // 定义特征向量/数组 - int[] v = new int[this.hashbits]; - // 英文分词 - // StringTokenizer stringTokens = new StringTokenizer(this.tokens); - // while (stringTokens.hasMoreTokens()) { - // String temp = stringTokens.nextToken(); - // } - // 1、中文分词,分词器采用 IKAnalyzer3.2.8 ,仅供演示使用,新版 API 已变化。 - StringReader reader = new StringReader(this.tokens); - // 当为true时,分词器进行最大词长切分 - IKSegmentation ik = new IKSegmentation(reader, true); - Lexeme lexeme = null; - String word = null; - while ((lexeme = ik.next()) != null) { - word = lexeme.getLexemeText(); -// System.out.println(word); - // 注意停用词会被干掉 - // System.out.println(word); - // 2、将每一个分词hash为一组固定长度的数列.比如 64bit 的一个整数. - BigInteger t = this.hash(word); - for (int i = 0; i < this.hashbits; i++) { - BigInteger bitmask = new BigInteger("1").shiftLeft(i); - // 3、建立一个长度为64的整数数组(假设要生成64位的数字指纹,也可以是其它数字), - // 对每一个分词hash后的数列进行判断,如果是1000...1,那么数组的第一位和末尾一位加1, - // 中间的62位减一,也就是说,逢1加1,逢0减1.一直到把所有的分词hash数列全部判断完毕. - if (t.and(bitmask).signum() != 0) { - // 这里是计算整个文档的所有特征的向量和 - // 这里实际使用中需要 +- 权重,比如词频,而不是简单的 +1/-1, - v[i] += 1; - } else { - v[i] -= 1; - } - } - } - - BigInteger fingerprint = new BigInteger("0"); - StringBuffer simHashBuffer = new StringBuffer(); - for (int i = 0; i < this.hashbits; i++) { - // 4、最后对数组进行判断,大于0的记为1,小于等于0的记为0,得到一个 64bit 的数字指纹/签名. - if (v[i] >= 0) { - fingerprint = fingerprint.add(new BigInteger("1").shiftLeft(i)); - simHashBuffer.append("1"); - } else { - simHashBuffer.append("0"); - } - } - this.strSimHash = simHashBuffer.toString(); - System.out.println(this.strSimHash + " length " + this.strSimHash.length()); - return fingerprint; - } - - private BigInteger hash(String source) { - if (source == null || source.length() == 0) { - return new BigInteger("0"); - } else { - char[] sourceArray = source.toCharArray(); - BigInteger x = BigInteger.valueOf(((long) sourceArray[0]) << 7); - BigInteger m = new BigInteger("1000003"); - BigInteger mask = new BigInteger("2").pow(this.hashbits).subtract(new BigInteger("1")); - for (char item : sourceArray) { - BigInteger temp = BigInteger.valueOf((long) item); - x = x.multiply(m).xor(temp).and(mask); - } - x = x.xor(new BigInteger(String.valueOf(source.length()))); - if (x.equals(new BigInteger("-1"))) { - x = new BigInteger("-2"); - } - return x; - } - } - - public int hammingDistance(SimHash other) { - - BigInteger x = this.intSimHash.xor(other.intSimHash); - int tot = 0; - - // 统计x中二进制位数为1的个数 - // 我们想想,一个二进制数减去1,那么,从最后那个1(包括那个1)后面的数字全都反了, - // 对吧,然后,n&(n-1)就相当于把后面的数字清0, - // 我们看n能做多少次这样的操作就OK了。 - - while (x.signum() != 0) { - tot += 1; - x = x.and(x.subtract(new BigInteger("1"))); - } - return tot; - } - - public int getDistance(String str1, String str2) { - int distance; - if (str1.length() != str2.length()) { - distance = -1; - } else { - distance = 0; - for (int i = 0; i < str1.length(); i++) { - if (str1.charAt(i) != str2.charAt(i)) { - distance++; - } - } - } - return distance; - } - - public List subByDistance(SimHash simHash, int distance) { - // 分成几组来检查 - int numEach = this.hashbits / (distance + 1); - List characters = new ArrayList(); - - StringBuffer buffer = new StringBuffer(); - - for (int i = 0; i < this.intSimHash.bitLength(); i++) { - // 当且仅当设置了指定的位时,返回 true - boolean sr = simHash.intSimHash.testBit(i); - - if (sr) { - buffer.append("1"); - } else { - buffer.append("0"); - } - - if ((i + 1) % numEach == 0) { - // 将二进制转为BigInteger - BigInteger eachValue = new BigInteger(buffer.toString(), 2); - System.out.println("----" + eachValue); - buffer.delete(0, buffer.length()); - characters.add(eachValue); - } - } - - return characters; - } - - public static void main(String[] args) throws IOException { - String s = "传统的 hash 算法只负责将原始内容尽量均匀随机地映射为一个签名值," + "原理上相当于伪随机数产生算法。产生的两个签名,如果相等,说明原始内容在一定概 率 下是相等的;" - + "如果不相等,除了说明原始内容不相等外,不再提供任何信息,因为即使原始内容只相差一个字节," + "所产生的签名也很可能差别极大。从这个意义 上来 说,要设计一个 hash 算法," - + "对相似的内容产生的签名也相近,是更为艰难的任务,因为它的签名值除了提供原始内容是否相等的信息外," + "还能额外提供不相等的 原始内容的差异程度的信息。"; - SimHash hash1 = new SimHash(s, 64); - System.out.println(hash1.intSimHash + " " + hash1.intSimHash.bitLength()); - // 计算 海明距离 在 3 以内的各块签名的 hash 值 - hash1.subByDistance(hash1, 3); - - // 删除首句话,并加入两个干扰串 - s = "原理上相当于伪随机数产生算法。产生的两个签名,如果相等,说明原始内容在一定概 率 下是相等的;" - + "如果不相等,除了说明原始内容不相等外,不再提供任何信息,因为即使原始内容只相差一个字节," + "所产生的签名也很可能差别极大。从这个意义 上来 说,要设计一个 hash 算法," - + "对相似的内容产生的签名也相近,是更为艰难的任务,因为它的签名值除了提供原始内容是否相等的信息外," + "干扰1还能额外提供不相等的 原始内容的差异程度的信息。"; - SimHash hash2 = new SimHash(s, 64); - System.out.println(hash2.intSimHash + " " + hash2.intSimHash.bitCount()); - hash1.subByDistance(hash2, 3); - - // 首句前添加一句话,并加入四个干扰串 - s = "imhash算法的输入是一个向量,输出是一个 f 位的签名值。为了陈述方便," + "假设输入的是一个文档的特征集合,每个特征有一定的权重。" - + "传统干扰4的 hash 算法只负责将原始内容尽量均匀随机地映射为一个签名值," + "原理上这次差异有多大呢3相当于伪随机数产生算法。产生的两个签名,如果相等," - + "说明原始内容在一定概 率 下是相等的;如果不相等,除了说明原始内容不相等外,不再提供任何信息," + "因为即使原始内容只相差一个字节,所产生的签名也很可能差别极大。从这个意义 上来 说," - + "要设计一个 hash 算法,对相似的内容产生的签名也相近,是更为艰难的任务,因为它的签名值除了提供原始" + "内容是否相等的信息外,干扰1还能额外提供不相等的 原始再来干扰2内容的差异程度的信息。"; - SimHash hash3 = new SimHash(s, 64); - System.out.println(hash3.intSimHash + " " + hash3.intSimHash.bitCount()); - hash1.subByDistance(hash3, 3); - - System.out.println("============================"); - - int dis = hash1.getDistance(hash1.strSimHash, hash2.strSimHash); - System.out.println(hash1.hammingDistance(hash2) + " " + dis); - // 根据鸽巢原理(也成抽屉原理,见组合数学),如果两个签名的海明距离在 3 以内,它们必有一块签名subByDistance()完全相同。 - int dis2 = hash1.getDistance(hash1.strSimHash, hash3.strSimHash); - System.out.println(hash1.hammingDistance(hash3) + " " + dis2); - } -} \ No newline at end of file diff --git a/quick-simhash/src/main/java/com/quick/simhash/SimHashTool.java b/quick-simhash/src/main/java/com/quick/simhash/SimHashTool.java deleted file mode 100644 index de6812bb..00000000 --- a/quick-simhash/src/main/java/com/quick/simhash/SimHashTool.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.quick.simhash; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; - -/** - * 相似哈希算法工具类 - * - * @author lyq - * - */ -public class SimHashTool { - // 二进制哈希位数 - private int hashBitNum; - // 相同位数最小阈值 - private double minSupportValue; - - public SimHashTool(int hashBitNum, double minSupportValue) { - this.hashBitNum = hashBitNum; - this.minSupportValue = minSupportValue; - } - - /** - * 比较文章的相似度 - * - * @param newsPath1 - * 文章路径1 - * @param newsPath2 - * 文章路径2 - */ - public void compareArticals(String newsPath1, String newsPath2) { - String content1; - String content2; - int sameNum; - int[] hashArray1; - int[] hashArray2; - - - // 读取分词结果 - content1 = readDataFile(newsPath1); - content2 = readDataFile(newsPath2); - hashArray1 = calSimHashValue(content1); - hashArray2 = calSimHashValue(content2); - - // 比较哈希位数相同个数 - sameNum = 0; - for (int i = 0; i < hashBitNum; i++) { - if (hashArray1[i] == hashArray2[i]) { - sameNum++; - } - } - - // 与最小阈值进行比较 - if (sameNum > this.hashBitNum * this.minSupportValue) { - System.out.println(String.format("相似度为%s,超过阈值%s,所以新闻1与新闻2是相似的", - sameNum * 1.0 / hashBitNum, minSupportValue)); - } else { - System.out.println(String.format("相似度为%s,小于阈值%s,所以新闻1与新闻2不是相似的", - sameNum * 1.0 / hashBitNum, minSupportValue)); - } - } - - /** - * 计算文本的相似哈希值 - * - * @param content - * 新闻内容数据 - * @return - */ - private int[] calSimHashValue(String content) { - int index; - long hashValue; - double weight; - int[] binaryArray; - int[] resultValue; - double[] hashArray; - String w; - String[] words; - News news; - - news = new News(content); - news.statWords(); - hashArray = new double[hashBitNum]; - resultValue = new int[hashBitNum]; - - words = content.split(" "); - for (String str : words) { - index = str.indexOf('/'); - if (index == -1) { - continue; - } - w = str.substring(0, index); - - // 获取权重值,根据词频所得 - weight = news.getWordFrequentValue(w); - if(weight == -1){ - continue; - } - // 进行哈希值的计算 - hashValue = BKDRHash(w); - // 取余把位数变为n位 - hashValue %= Math.pow(2, hashBitNum); - - // 转为二进制的形式 - binaryArray = new int[hashBitNum]; - numToBinaryArray(binaryArray, (int) hashValue); - - for (int i = 0; i < binaryArray.length; i++) { - // 如果此位置上为1,加权重 - if (binaryArray[i] == 1) { - hashArray[i] += weight; - } else { - // 为0则减权重操作 - hashArray[i] -= weight; - } - } - } - - // 进行数组收缩操作,根据值的正负号,重新改为二进制数据形式 - for (int i = 0; i < hashArray.length; i++) { - if (hashArray[i] > 0) { - resultValue[i] = 1; - } else { - resultValue[i] = 0; - } - } - - return resultValue; - } - - /** - * 数字转为二进制形式 - * - * @param binaryArray - * 转化后的二进制数组形式 - * @param num - * 待转化数字 - */ - private void numToBinaryArray(int[] binaryArray, int num) { - int index = 0; - int temp = 0; - while (num != 0) { - binaryArray[index] = num % 2; - index++; - num /= 2; - } - - // 进行数组前和尾部的调换 - for (int i = 0; i < binaryArray.length / 2; i++) { - temp = binaryArray[i]; - binaryArray[i] = binaryArray[binaryArray.length - 1 - i]; - binaryArray[binaryArray.length - 1 - i] = temp; - } - } - - /** - * BKDR字符哈希算法 - * - * @param str - * @return - */ - public static long BKDRHash(String str) { - int seed = 31; /* 31 131 1313 13131 131313 etc.. */ - long hash = 0; - int i = 0; - - for (i = 0; i < str.length(); i++) { - hash = (hash * seed) + (str.charAt(i)); - } - - hash = Math.abs(hash); - return hash; - } - - /** - * 从文件中读取数据 - */ - private String readDataFile(String filePath) { - File file = new File(filePath); - StringBuilder strBuilder = null; - - try { - BufferedReader in = new BufferedReader(new FileReader(file)); - String str; - strBuilder = new StringBuilder(); - while ((str = in.readLine()) != null) { - strBuilder.append(str); - } - in.close(); - } catch (IOException e) { - e.getStackTrace(); - } - - return strBuilder.toString(); - } - - /** - * 利用分词系统进行新闻内容的分词 - * - * @param srcPath - * 新闻文件路径 - */ - private void parseNewsContent(String srcPath) { - // TODO Auto-generated method stub - int index; - String dirApi; - String desPath; - - dirApi = System.getProperty("user.dir") + "\\lib"; - // 组装输出路径值 - index = srcPath.indexOf('.'); - desPath = srcPath.substring(0, index) + "-split.txt"; - - try { - ICTCLAS50 testICTCLAS50 = new ICTCLAS50(); - // 分词所需库的路径、初始化 - if (testICTCLAS50.ICTCLAS_Init(dirApi.getBytes("GB2312")) == false) { - System.out.println("Init Fail!"); - return; - } - // 将文件名string类型转为byte类型 - byte[] Inputfilenameb = srcPath.getBytes(); - - // 分词处理后输出文件名、将文件名string类型转为byte类型 - byte[] Outputfilenameb = desPath.getBytes(); - - // 文件分词(第一个参数为输入文件的名,第二个参数为文件编码类型,第三个参数为是否标记词性集1 yes,0 - // no,第四个参数为输出文件名) - testICTCLAS50.ICTCLAS_FileProcess(Inputfilenameb, 0, 1, - Outputfilenameb); - // 退出分词器 - testICTCLAS50.ICTCLAS_Exit(); - } catch (Exception ex) { - ex.printStackTrace(); - } - - } - -} \ No newline at end of file diff --git a/quick-simhash/src/main/resources/IKAnalyzer.cfg.xml b/quick-simhash/src/main/resources/IKAnalyzer.cfg.xml deleted file mode 100644 index c26bc4ab..00000000 --- a/quick-simhash/src/main/resources/IKAnalyzer.cfg.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - IK Analyzer 扩展配置 - - - stopword.dic; - - \ No newline at end of file diff --git a/quick-simhash/src/main/resources/stopword.dic b/quick-simhash/src/main/resources/stopword.dic deleted file mode 100644 index c1b994bc..00000000 --- a/quick-simhash/src/main/resources/stopword.dic +++ /dev/null @@ -1,33 +0,0 @@ -a -an -and -are -as -at -be -but -by -for -if -in -into -is -it -no -not -of -on -or -such -that -the -their -then -there -these -they -this -to -was -will -with \ No newline at end of file diff --git a/quick-spark/pom.xml b/quick-spark/pom.xml deleted file mode 100644 index e88c470b..00000000 --- a/quick-spark/pom.xml +++ /dev/null @@ -1,162 +0,0 @@ - - - - - quick-spark - com.quick - 1.0-SNAPSHOT - jar - 4.0.0 - - - - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - - - - - 2.10.4 - 1.6.2 - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - - - org.springframework.boot - spring-boot-starter-log4j - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.scala-lang - scala-library - ${scala.version} - - - com.fasterxml.jackson.core - jackson-databind - 2.4.4 - - - org.apache.spark - spark-core_2.10 - ${spark.version} - - - org.slf4j - slf4j-log4j12 - - - log4j - log4j - - - - - org.apache.spark - spark-launcher_2.10 - ${spark.version} - - - org.apache.spark - spark-mllib_2.10 - ${spark.version} - - - org.apache.spark - spark-streaming_2.10 - ${spark.version} - - - junit - junit - 4.4 - test - - - org.specs - specs - 1.2.5 - test - - - - org.ansj - ansj_seg - 5.1.1 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.8.1 - - - **/*.java - **/*.scala - - - - - org.scala-tools - maven-scala-plugin - 2.15.2 - - - scala-compile-first - process-resources - - compile - - - - scala-test-compile - process-test-resources - - testCompile - - - - - - - - \ No newline at end of file diff --git a/quick-spark/src/main/java/spark/config/ApplicationConfig.java b/quick-spark/src/main/java/spark/config/ApplicationConfig.java deleted file mode 100644 index 16cbbead..00000000 --- a/quick-spark/src/main/java/spark/config/ApplicationConfig.java +++ /dev/null @@ -1,68 +0,0 @@ -package spark.config; - -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaSparkContext; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; -import org.springframework.core.env.Environment; -import org.springframework.util.ResourceUtils; - -import java.io.File; -import java.io.FileNotFoundException; - -/** - * Created by achat1 on 9/22/15. - */ -@Configuration -public class ApplicationConfig { - - @Autowired - private Environment env; - - @Value("${spark.app.name}") - private String appName; - - @Value("${spark.home}") - private String sparkHome; - - @Value("${spark.master.uri}") - private String masterUri; - - @Bean - public SparkConf sparkConf() throws FileNotFoundException { - File file = ResourceUtils.getFile("classpath:file/winutils.exe"); - System.out.println(appName); - System.out.println(sparkHome); - System.out.println(masterUri); - SparkConf sparkConf = new SparkConf() - .setAppName(appName) - .setSparkHome(sparkHome) - .setMaster(masterUri) - .set("spark.testing.memory", "2147480000"); - - return sparkConf; - } - - @Bean - public JavaSparkContext javaSparkContext() throws FileNotFoundException { - return new JavaSparkContext(sparkConf()); - } -// -// @Bean -// public SparkSession sparkSession() { -// return SparkSession -// .builder() -// .sparkContext(javaSparkContext().sc()) -// .appName("Java Spark SQL basic example") -// .getOrCreate(); -// } - - @Bean - public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { - return new PropertySourcesPlaceholderConfigurer(); - } - -} \ No newline at end of file diff --git a/quick-spark/src/main/java/spark/controller/WebController.java b/quick-spark/src/main/java/spark/controller/WebController.java deleted file mode 100644 index 04fceb0f..00000000 --- a/quick-spark/src/main/java/spark/controller/WebController.java +++ /dev/null @@ -1,51 +0,0 @@ -package spark.controller; - -import org.apache.log4j.Logger; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; -import spark.service.WordCountService; -import spark.util.BaseResp; -import spark.util.ResultStatus; - -import javax.annotation.Resource; -import java.io.FileNotFoundException; -import java.util.Map; - -/** - * Created with IDEA - * User: vector - * Data: 2017/4/13 - * Time: 18:02 - * Description: - */ -@RestController - @RequestMapping("/spark") -public class WebController { - private Logger logger = Logger.getLogger(WebController.class); - - @Resource - private WordCountService wordCountService; - - @RequestMapping("/wordCount") - @ResponseBody - public BaseResp> wordCount(){ - - logger.info("start submit spark tast..."); - Map counts = null; - try { - counts = wordCountService.run(); - } catch (FileNotFoundException e) { - return new BaseResp<>(ResultStatus.error_record_not_found); - } - - return new BaseResp>(ResultStatus.SUCCESS,counts); - } - - - - @RequestMapping("/hello") - public BaseResp pring(){ - return new BaseResp(ResultStatus.SUCCESS,"hihi"); - } -} diff --git a/quick-spark/src/main/java/spark/service/WordCountService.java b/quick-spark/src/main/java/spark/service/WordCountService.java deleted file mode 100644 index f3ad80df..00000000 --- a/quick-spark/src/main/java/spark/service/WordCountService.java +++ /dev/null @@ -1,68 +0,0 @@ -package spark.service; - -import org.apache.spark.api.java.JavaPairRDD; -import org.apache.spark.api.java.JavaRDD; -import org.apache.spark.api.java.JavaSparkContext; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.util.ResourceUtils; -import scala.Serializable; -import scala.Tuple2; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -@Component -public class WordCountService implements Serializable { - private static final Pattern SPACE = Pattern.compile(" "); - - @Autowired - private transient JavaSparkContext sc; - - public Map run() throws FileNotFoundException { - Map result = new HashMap<>(); - File file = ResourceUtils.getFile("classpath:blsmy.txt"); - JavaRDD lines = sc.textFile(file.getAbsolutePath()); - JavaRDD words = lines.flatMap(word-> Arrays.asList(SPACE.split(word))); - JavaPairRDD ones = words.mapToPair(s->new Tuple2<>(s, 1)); - JavaPairRDD counts = ones.reduceByKey((Integer i1, Integer i2)->(i1 + i2)); - List> output = counts.collect(); - output.forEach(item->result.put(item._1(),item._2())); - -/** - JavaRDD words = lines.flatMap(new FlatMapFunction() { - @Override - public Iterable call(String s) throws Exception { - return Arrays.asList(SPACE.split(s)); - } - }); - JavaPairRDD ones = words.mapToPair(new PairFunction() { - private static final long serialVersionUID = 1L; - public Tuple2 call(String s) { - return new Tuple2(s, 1); - } - }); - JavaPairRDD counts = ones.reduceByKey(new Function2() { - private static final long serialVersionUID = 1L; - - public Integer call(Integer i1, Integer i2) { - return i1 + i2; - } - }); - List> output = counts.collect(); - for (Tuple2 tuple : output) { - result.put(tuple._1(), tuple._2()); - } - */ - return result; - - } -} - - - diff --git a/quick-spark/src/main/java/spark/test/java/EmailFilter.java b/quick-spark/src/main/java/spark/test/java/EmailFilter.java deleted file mode 100644 index b87cad5f..00000000 --- a/quick-spark/src/main/java/spark/test/java/EmailFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -package spark.test.java; - -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaRDD; -import org.apache.spark.api.java.JavaSparkContext; -import org.apache.spark.mllib.classification.LogisticRegressionModel; -import org.apache.spark.mllib.classification.LogisticRegressionWithSGD; -import org.apache.spark.mllib.feature.HashingTF; -import org.apache.spark.mllib.linalg.Vector; -import org.apache.spark.mllib.regression.LabeledPoint; - -import java.util.Arrays; - -/** - * Created with IDEA - * User: vector - * Data: 2017/5/5 - * Time: 10:34 - * Description: - */ -public class EmailFilter { - public static void main(String[] args) { - SparkConf conf = new SparkConf().setMaster("local").setAppName("垃圾邮件分类"); - JavaSparkContext sc = new JavaSparkContext(conf); - JavaRDD ham = sc.textFile("D:\\githubspace\\springbootquick\\src\\main\\resources\\ham.txt"); - JavaRDD spam = sc.textFile("D:\\githubspace\\springbootquick\\src\\main\\resources\\spam.txt"); - - final HashingTF tf = new HashingTF(10000); - - JavaRDD posExamples = spam.map(h->new LabeledPoint(1,tf.transform(Arrays.asList(h.split(" "))))); - JavaRDD negExamples = ham.map(s->new LabeledPoint(0,tf.transform(Arrays.asList(s.split(" "))))); - - - JavaRDD trainingData = posExamples.union(negExamples); - trainingData.cache(); - - LogisticRegressionWithSGD lrLearner = new LogisticRegressionWithSGD(); - LogisticRegressionModel model = lrLearner.run(trainingData.rdd()); - - Vector posTestExample = tf.transform(Arrays.asList("O M G GET cheap stuff by sending money to ...".split(" "))); - - System.out.println(posTestExample.toJson()); - - Vector negTestExample = tf.transform(Arrays.asList("Hi Dad, I started studying Spark the other ...".split(" "))); - - System.out.println("Prediction for positive test example: " + model.predict(posTestExample)); - System.out.println("Prediction for negative test example: " + model.predict(negTestExample)); - - - } -} diff --git a/quick-spark/src/main/java/spark/test/java/Test.java b/quick-spark/src/main/java/spark/test/java/Test.java deleted file mode 100644 index 9e2a2f4c..00000000 --- a/quick-spark/src/main/java/spark/test/java/Test.java +++ /dev/null @@ -1,22 +0,0 @@ -package spark.test.java; - -import org.apache.spark.mllib.linalg.Vector; -import org.apache.spark.mllib.linalg.Vectors; - -/** - * Created with IDEA - * User: vector - * Data: 2017/5/5 - * Time: 11:50 - * Description: - */ -public class Test { - public static void main(String[] args) { - // 稠密向量 - Vector denseVec = Vectors.dense(1.0,2.0,3.0); - System.out.println(denseVec); - // 稠密向量 - Vector sparseVec = Vectors.sparse(4,new int[]{0,2},new double[]{1.0,2.0}); - System.out.println(sparseVec); - } -} diff --git a/quick-spark/src/main/java/spark/test/java/Word2VecTest.java b/quick-spark/src/main/java/spark/test/java/Word2VecTest.java deleted file mode 100644 index f149fccb..00000000 --- a/quick-spark/src/main/java/spark/test/java/Word2VecTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package spark.test.java; - -import com.google.common.base.Strings; -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaRDD; -import org.apache.spark.api.java.JavaSparkContext; -import org.apache.spark.mllib.feature.Word2Vec; -import org.apache.spark.mllib.feature.Word2VecModel; -import org.apache.spark.sql.SQLContext; -import scala.Tuple2; - -import java.util.Arrays; -import java.util.List; - -/** - * Created with IDEA - * User: vector - * Data: 2017/5/5 - * Time: 14:07 - * Description: - */ -public class Word2VecTest { - public static void main(String[] args) { - - - } - - static void trainModelAndDo(){ - SparkConf conf = new SparkConf().setMaster("local").setAppName("Word2Vec"); - JavaSparkContext sc = new JavaSparkContext(conf); - SQLContext sqlContext = new SQLContext(sc); - String sentence = Strings.repeat("a b ", 100) + Strings.repeat("a c ", 10); - List words = Arrays.asList(sentence.split(" ")); - List> localDoc = Arrays.asList(words, words); - JavaRDD> doc = sc.parallelize(localDoc); - Word2Vec word2vec = new Word2Vec() - .setVectorSize(10) - .setSeed(42L); - Word2VecModel model = word2vec.fit(doc); -// model.save(sc.sc(),"D:\\data\\sparkModel"); - Tuple2[] syms = model.findSynonyms("a", 2); - System.out.println(syms.length); - System.out.println(syms[0]._1()); - System.out.println(syms[1]._1()); - } - - static void loadModelAndDo(){ - SparkConf conf = new SparkConf().setMaster("local").setAppName("Word2Vec"); - JavaSparkContext sc = new JavaSparkContext(conf); - SQLContext sqlContext = new SQLContext(sc); -// sqlContext. - } -} diff --git a/quick-spark/src/main/java/spark/test/scala/LinearRegression.scala b/quick-spark/src/main/java/spark/test/scala/LinearRegression.scala deleted file mode 100644 index 2636b432..00000000 --- a/quick-spark/src/main/java/spark/test/scala/LinearRegression.scala +++ /dev/null @@ -1,33 +0,0 @@ -package com.spark.scala - -import org.apache.spark.mllib.linalg.Vectors -import org.apache.spark.mllib.regression.{LabeledPoint, LinearRegressionWithSGD} -import org.apache.spark.{SparkConf, SparkContext} - -/** - * Created with IDEA - * User: vector - * Data: 2017/5/5 - * Time: 9:58 - * Description: - */ -object LinearRegression { - val conf = new SparkConf().setMaster("local").setAppName("LinearRegression") - val sc = new SparkContext(conf) - - def main(args: Array[String]): Unit = { - val data = sc.textFile("D:\\githubspace\\springbootquick\\src\\main\\java\\com\\quick\\scala\\lr.txt") - val parsedData = data.map { line => - val parts = line.split('|') - LabeledPoint(parts(0).toDouble, Vectors.dense(parts(1).split(',').map(_.toDouble))) - }.cache() - - val model = LinearRegressionWithSGD.train(parsedData, 2, 0.1) - - val result = model.predict(Vectors.dense(1, 3)) - - println(model.weights) - println(model.weights.size) - println(result) - } -} diff --git a/quick-spark/src/main/java/spark/test/scala/lr.txt b/quick-spark/src/main/java/spark/test/scala/lr.txt deleted file mode 100644 index 8e71c443..00000000 --- a/quick-spark/src/main/java/spark/test/scala/lr.txt +++ /dev/null @@ -1,5 +0,0 @@ -5|1,1 -8|1,2 -7|2,1 -13|2,3 -18|3,4 \ No newline at end of file diff --git a/quick-spark/src/main/java/spark/textmatch/SimHash.java b/quick-spark/src/main/java/spark/textmatch/SimHash.java deleted file mode 100644 index 7b72858e..00000000 --- a/quick-spark/src/main/java/spark/textmatch/SimHash.java +++ /dev/null @@ -1,188 +0,0 @@ -package spark.textmatch; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; -import java.util.StringTokenizer; - -public class SimHash { - - private String tokens; - - private BigInteger intSimHash; - - private String strSimHash; - - private int hashbits = 64; - - public SimHash(String tokens) { - this.tokens = tokens; - this.intSimHash = this.simHash(); - } - - public SimHash(String tokens, int hashbits) { - this.tokens = tokens; - this.hashbits = hashbits; - this.intSimHash = this.simHash(); - } - - public BigInteger simHash() { - int[] v = new int[this.hashbits]; - StringTokenizer stringTokens = new StringTokenizer(this.tokens); - while (stringTokens.hasMoreTokens()) { - String temp = stringTokens.nextToken(); - BigInteger t = this.hash(temp); - for (int i = 0; i < this.hashbits; i++) { - BigInteger bitmask = new BigInteger("1").shiftLeft(i); - if (t.and(bitmask).signum() != 0) { - v[i] += 1; - } else { - v[i] -= 1; - } - } - } - BigInteger fingerprint = new BigInteger("0"); - StringBuffer simHashBuffer = new StringBuffer(); - for (int i = 0; i < this.hashbits; i++) { - if (v[i] >= 0) { - fingerprint = fingerprint.add(new BigInteger("1").shiftLeft(i)); - simHashBuffer.append("1"); - }else{ - simHashBuffer.append("0"); - } - } - this.strSimHash = simHashBuffer.toString(); - System.out.println(this.strSimHash + " length " + this.strSimHash.length()); - return fingerprint; - } - - private BigInteger hash(String source) { - if (source == null || source.length() == 0) { - return new BigInteger("0"); - } else { - char[] sourceArray = source.toCharArray(); - BigInteger x = BigInteger.valueOf(((long) sourceArray[0]) << 7); - BigInteger m = new BigInteger("1000003"); - BigInteger mask = new BigInteger("2").pow(this.hashbits).subtract( - new BigInteger("1")); - for (char item : sourceArray) { - BigInteger temp = BigInteger.valueOf((long) item); - x = x.multiply(m).xor(temp).and(mask); - } - x = x.xor(new BigInteger(String.valueOf(source.length()))); - if (x.equals(new BigInteger("-1"))) { - x = new BigInteger("-2"); - } - return x; - } - } - - /** - * 取两个二进制的异或,统计为1的个数,就是海明距离 - * @param other - * @return - */ - - public int hammingDistance(SimHash other) { - - BigInteger x = this.intSimHash.xor(other.intSimHash); - int tot = 0; - - //统计x中二进制位数为1的个数 - //我们想想,一个二进制数减去1,那么,从最后那个1(包括那个1)后面的数字全都反了,对吧,然后,n&(n-1)就相当于把后面的数字清0, - //我们看n能做多少次这样的操作就OK了。 - - while (x.signum() != 0) { - tot += 1; - x = x.and(x.subtract(new BigInteger("1"))); - } - return tot; - } - - /** - * calculate Hamming Distance between two strings - * 二进制怕有错,当成字符串,作一个,比较下结果 - * @author - * @param str1 the 1st string - * @param str2 the 2nd string - * @return Hamming Distance between str1 and str2 - */ - public int getDistance(String str1, String str2) { - int distance; - if (str1.length() != str2.length()) { - distance = -1; - } else { - distance = 0; - for (int i = 0; i < str1.length(); i++) { - if (str1.charAt(i) != str2.charAt(i)) { - distance++; - } - } - } - return distance; - } - - /** - * 如果海明距离取3,则分成四块,并得到每一块的bigInteger值 ,作为索引值使用 - * @param simHash - * @param distance - * @return - */ - public List subByDistance(SimHash simHash, int distance){ - int numEach = this.hashbits/(distance+1); - List characters = new ArrayList(); - - StringBuffer buffer = new StringBuffer(); - - int k = 0; - for( int i = 0; i < this.intSimHash.bitLength(); i++){ - boolean sr = simHash.intSimHash.testBit(i); - - if(sr){ - buffer.append("1"); - } - else{ - buffer.append("0"); - } - - if( (i+1)%numEach == 0 ){ - BigInteger eachValue = new BigInteger(buffer.toString(),2); - System.out.println("----" +eachValue ); - buffer.delete(0, buffer.length()); - characters.add(eachValue); - } - } - - return characters; - } - - public static void main(String[] args) { - String s = "This is a test string for testing"; - - SimHash hash1 = new SimHash(s, 64); - System.out.println(hash1.intSimHash + " " + hash1.intSimHash.bitLength()); - - hash1.subByDistance(hash1, 3); - - System.out.println("\n"); - s = "This is a test string for testing, This is a test string for testing abcdef"; - SimHash hash2 = new SimHash(s, 64); - System.out.println(hash2.intSimHash+ " " + hash2.intSimHash.bitCount()); - hash1.subByDistance(hash2, 3); - s = "This is a test string for testing als"; - SimHash hash3 = new SimHash(s, 64); - System.out.println(hash3.intSimHash+ " " + hash3.intSimHash.bitCount()); - hash1.subByDistance(hash3, 3); - System.out.println("============================"); - int dis = hash1.getDistance(hash1.strSimHash,hash2.strSimHash); - - System.out.println(hash1.hammingDistance(hash2) + " "+ dis); - - int dis2 = hash1.getDistance(hash1.strSimHash,hash3.strSimHash); - - System.out.println(hash1.hammingDistance(hash3) + " " + dis2); - - - - } -} \ No newline at end of file diff --git a/quick-spark/src/main/java/spark/textmatch/TextMatch.scala b/quick-spark/src/main/java/spark/textmatch/TextMatch.scala deleted file mode 100644 index 303563b9..00000000 --- a/quick-spark/src/main/java/spark/textmatch/TextMatch.scala +++ /dev/null @@ -1,32 +0,0 @@ -package com.spark.textmatch - -import org.apache.spark.{SparkConf, SparkContext} - -/** - * Created by wangxc on 2017/5/13. - */ -object TextMatch { - def main(args: Array[String]): Unit = { - val conf = new SparkConf().setAppName("TextMatch").setMaster("local[4]") - val sc = new SparkContext(conf) - val singles = Array("this", "is") - - val sentences = Array("this Date", - "is there something", - "where are something", - "this is a string") - - val rdd = sc.parallelize(sentences) // create RDD - - val keys = singles.toSet // words required as keys. - - val result = rdd.flatMap { sen => - val words = sen.split(" ").toSet; - val common = keys & words; // intersect - common.map(x => (x, sen)) // map as key -> sen - }.groupByKey.mapValues(_.toArray) // group values for a key - .collect - println(result.length) - } - -} diff --git a/quick-spark/src/main/resources/application.properties b/quick-spark/src/main/resources/application.properties deleted file mode 100644 index b6f31618..00000000 --- a/quick-spark/src/main/resources/application.properties +++ /dev/null @@ -1,8 +0,0 @@ - - -server.port=8008 - - -spark.app.name=springbootspark -spark.home=D:\\develop\\tools\\spark-1.6.2-bin-hadoop2.6\\spark-1.6.2-bin-hadoop2.6 -spark.master.uri=local[3] \ No newline at end of file diff --git a/quick-spark/src/main/resources/blsmy.txt b/quick-spark/src/main/resources/blsmy.txt deleted file mode 100644 index d1480dae..00000000 --- a/quick-spark/src/main/resources/blsmy.txt +++ /dev/null @@ -1,3986 +0,0 @@ -ʥĸԺӢİ - - -Authors Biography -Victor Hugo was born on the 26th of February, 1802, at Besan?on, where his father, an officer in Moreaus army, was commanding a battalion. His first three years were spent in Corsica; in 1805 his mother took her family to Paris, but rejoined her husband, at Avellino, in South Italy, in 1807. General Hugo, as the father presently became, was appointed a governor in Spain, from which the English under Wellington dislodged himself and his family in 1812. Returning to Paris, Victor was put to school until 1818, when he made up his mind that literature should be his profession. That he was still under classical influences was proved by his first volume, the TXTأ뵽http://www.bbsxiaoshuo.com - -Odes et Posies of 1822. It gained him a pension from Louis XVIII, and was followed next year by his earliest novel, Han dIslande. The romantic movement now spread to France, and Hugo was one of its earliest adherents; he took the lead in revolt by publishing his Odes in 1826 and his Cromwell in 1827. It was in the preface to the latter that his famous formula of the Romantic faith was issued. He first took a place, however, among the leading lyric poets of Europe with his Orientales of 1829. This was a crowning year in Victor Hugos career; it saw the production of Hernani and the composition of Marion Delorme and Le Dernier Jour dun Condamn. From this time forth his plays, novels, and lyrical poems were poured forth in three parallel and continuous streams. Notre-Dame de Paris was published on the 13th of February, 1831. For the next ten years the life of Victor Hugo was one of continual prosperity and ever-ascending fame. Among his dramas, Ruy Blas belongs to 1838, and among the collections of his poems Les Voix Intrieures to 1837. He was elected to the French Academy in 1841 and created a peer of France in 1845. He had been in sympathy with the Government, but when the Royalists fell and Napoleon arrived on the scene, Victor Hugo became a violent radical. As a member of the Legislative Assembly he opposed the Coup dtat, and was exiled at the close of 1851, taking up his residence in Brussels. Here the violence of his attacks on Napoleon III led to his expulsion from Belgium, and in the summer of 1852 he settled at St. Hliers, in Jersey, whence he sent out the fierce sheaf of Les Chatiments in the following year. Jersey, in its turn, became too hot to hold him, and in the autumn of 1855 he took up his abode at Hauteville House, Guernsey, where he resided for fifteen years. His occupation during the earlier part of his stay at Guernsey was the composition of La Lgende des Sicles, the first portion of which appeared in 1859. Victor Hugos huge novel, Les Misrables, was published in 1862, and his fantastic work on Shakespeare in 1864. Two grotesquely romantic novels, Les Travailleurs de la Mer and LHomme Qui Rit, belong respectively to 1866 and 1869. The Napoleon dynasty having fallen, Hugo immediately reappeared in France (September 5, 1870) and endured his share of the sufferings of the siege of Paris. After somewhat unsuccessfully acting a part in the politics of reconstruction, Hugo withdrew to Brussels, from which town he was driven in May, 1871, for his expressed sympathy with the Paris Commune. He now retired from the feuds of politics and devoted himself mainly to poetry, only one novel, Quatre-vingt-treize, 1874, belonging to this latest period of his career. From December, 1871, the residence of Hugo was Paris, where he lived with his widowed daughter-in-law and her children; in 1875 he was elected a perpetual senator. For fourteen years he enjoyed, in full serenity and strength, the splendours of an old age of extreme celebrity; it was said that he entered, during his lifetime, into immortality. In the spring of 1885 he took a chill while riding, as he loved to do, on the outside of an omnibus. His heart gradually gave way, and he died on the 22d of May. He received from the city of Paris a funeral of extreme pomp, and the Panthon was prepared for his reception, after the coffin had been lying in public state, for twenty-four hours, under the Arc de Triomphe. -E. G. -Author's Preface -To The Edition of 1832 -The announcement that this edition was furnished with several fresh chapters was incorrect; they should have been described as hitherto unpublished. For, if by fresh one understands newly written, then the chapters added to this edition are not fresh ones. They were written at the same time as the rest of the work; they date from the same period, were engendered by the same thought, and from the first formed part of the manuscript of Notre-Dame de Paris. Moreover, the author cannot imagine adding new developments to a work of this nature, the thing being once finished and done with. That cannot be done at will. To his idea, a novel is, in a sense, necessarily born with all its chapters complete, a drama with all its scenes. Do not let us think there is anything arbitrary in the particular number of parts which go to make up that wholethat mysterious microcosm which we call a novel or a drama. Neither joins nor patches are ever effectual in such a work, which ought to be fashioned in a single piece, and so be left, as best may be. The thing once done, listen to no second thoughts; attempt no touchings up of the book once given to the world, its sex, virile or otherwise, once recognised and acknowledged; the child, having once uttered its first cry, is born, is fashioned in that way and no other; father or mother are powerless to alter it, it belongs to the air and the sun; let it live or die as it is. Is your book a failure? Tant pis, but do not add chapters to those which have already failed. Is it defective?it should have been completed before birth. Your tree is gnarled? You will not straighten it out. Your novel phthisical, not viable? You will never give it the life that is lacking to it. Your drama is born lame? Believe me, it is futile to supply it with a wooden leg. -The author is therefore particularly anxious that the public should know that the interpolated chapters were not written expressly for this new edition. They were not included in the previous editions for a very simple reason. When Notre-Dame de Paris was being printed the first time, the packet of manuscript containing these chapters went astray, so that they would either have had to be rewritten or omitted. The author considered that the only chapters of real import were the two dealing specially with art and history, but that their omission would in no way disturb the course of the drama; and that the public being unconscious of their absence, he alone would be in the secret of this hiatus. He decided then for the omission, not only for the above reason, but because, it must be confessed, his indolence shrank affrighted from the task of rewriting the lost chapters. Rather would he have written a new book altogether. -Meanwhile, these chapters have reappeared, and the author seizes the first opportunity to restore them to their proper place, thus presenting his work completesuch as he imagined it, well or ill, lasting or perishable; but in the form he desired it to have. -Paris, October 20, 1832. -Author's Preface -To The Edition of 1831 -Some years ago, when visiting, or, more properly speaking, thoroughly exploring the Cathedral of Notre-Dame, the writer came upon the word -ANGKH1 -graven on the wall in a dim corner of one of the towers. -In the outline and slope of these Greek capitals, black with age and deeply scored into the stone, there were certain peculiarities characteristic of Gothic calligraphy which at once betrayed the hand of the medi?val scribe. -But most of all, the writer was struck by the dark and fateful significance of the word; and he pondered long and deeply over the identity of that anguished soul that would not quit the world without imprinting this stigma of crime or misfortune on the brow of the ancient edifice. -Since then the wall has been plastered over or scrapedI forget whichand the inscription has disappeared. For thus, during the past two hundred years, have the marvellous churches of the Middle Ages been treated. Defacement and mutilation have been their portionboth from within and from without. The priest plasters them over, the architect scrapes them; finally the people come and demolish them altogether. -Hence, save only the perishable memento dedicated to it here by the author of this book, nothing remains of the mysterious word graven on the sombre tower of Notre-Dame, nothing of the unknown destiny it so mournfully recorded. The man who inscribed that word passed centuries ago from among men; the word, in its turn, has been effaced from the wall of the Cathedral; soon, perhaps, the Cathedral itself will have vanished from the face of the earth. -This word, then, the writer has taken for the text of his book. -February, 1831. -___________________ -1 Fate, destiny. -BOOK I -Chapter 1 - The Great Hall -Precisely three hundred and forty-eight years six months and nineteen days ago1 Paris was awakened by the sound of the pealing of all the bells within the triple enclosing walls of the city, the Univeristy, and the town. -Yet the 6th of January, 1482, was not a day of which history has preserved the record. There was nothing of peculiar note in the event which set all the bells and the good people of Paris thus in motion from early dawn. It was neither an assault by Picards or Burgundians, nor a holy image carried in procession, nor a riot of the students in the vineyard of Laas, nor the entry into the city of our most dread Lord the King, nor even a fine stringing up of thieves, male and female, at the Justice of Paris. Neither was it the unexpected arrival, so frequent in the fifteenth century, of some foreign ambassador with his beplumed and gold-laced retinue. Scarce two days had elapsed since the last cavalcade of this description, that of the Flemish envoys charged with the mission to conclude the marriage between the Dauphin and Margaret of Flanders, had made its entry into Paris, to the great annoyance of Monsieur the Cardinal of Bourbon, who, to please the King, had been obliged to extend a gracious reception to this boorish company of Flemish burgomasters, and entertain them in his H?tel de Bourbon with a most pleasant morality play, drollery, and farce, while a torrent of rain drenched the splendid tapestries at his door. -The 6th of January, which set the whole population of Paris in a stir, as Jehan de Troyes relates, was the date of the double festivalunited since time immemorialof the Three Kings, and the Feast of Fools. -On this day there was invariably a bonfire on the Place de Grve, a may-pole in front of the Chapelle de Braque, and a mystery-play at the Palais de Justice, as had been proclaimed with blare of trumpets on the preceding day in all the streets by Monsieur the Provosts men, arrayed in tabards of violet camlet with great white crosses on the breast. -The stream of people accordingly made their way in the morning from all parts of the town, their shops and houses being closed, to one or other of these points named. Each one had chosen his share of the entertainmentssome the bonfire, some the may-pole, others the Mystery. To the credit of the traditional good sense of the Paris cit be it said that the majority of the spectators directed their steps towards the bonfire, which was entirely seasonable, or the Mystery, which was to be performed under roof and cover in the great Hall of the Palais de Justice, and were unanimous in leaving the poor scantily decked may-pole to shiver alone under the January sky in the cemetery of the Chapelle de Braque. -The crowd flocked thickest in the approaches to the Palais, as it was known that the Flemish envoys intended to be present at the performance of the Mystery, and the election of the Pope of Fools, which was likewise to take place in the great Hall. -It was no easy matter that day to penetrate into the great Hall, then reputed the largest roofed-in space in the world. (It is true that, at that time, Sauval had not yet measured the great hall of the Castle of Montargis.) To the gazers from the windows, the square in front of the Palais, packed as it was with people, presented the aspect of a lake into which five or six streets, like so many river mouths, were each moment pouring fresh floods of heads. The ever-swelling waves of this multitude broke against the angles of the houses, which projected here and there, like promontories, into the irregular basin of the Place. -In the centre of the high Gothic 2 fa?de of the Palais was the great flight of steps, incessantly occupied by a double stream ascending and descending, which, after being broken by the intermediate landing, spread in broad waves over the two lateral flights. -Down this great stair-case the crowd poured continuously into the Place like a cascade into a lake, the shouts, the laughter, the trampling of thousands of feet making a mighty clamour and tumult. From time to time the uproar redoubled, the current which bore the crowd towards the grand stairs was choked, thrown back, and formed into eddies, when some archer thrust back the crowd, or the horse of one of the provosts men kicked out to restore order; an admirable tradition which has been faithfully handed down through the centuries to our present gendarmerie of Paris. -Every door and window and roof swarmed with good, placid, honest burgher faces gazing at the Palais and at the crowd, and asking no better amusement. For there are many people in Paris quite content to be the spectators of spectators; and to us a wall, behind which something is going on, is a sufficiently exciting spectacle. -If we of the nineteenth century could mingle in imagination with these Parisians of the fifteenth century, could push our way with that hustling, elbowing, stamping crowd into the immense Hall of the Palais, so cramped on that 6th of January, 1482, the scene would not be without interest or charm for us, and we would find ourselves surrounded by things so old that to us they would appear quite new. -With the readers permission we will attempt to evoke in thought the impression he would have experienced in crossing with us the threshold of that great Hall amid that throng in surcoat, doublet, and kirtle. -At first there is nothing but a dull roar in our ears and a dazzle in our eyes. Overhead, a roof of double Gothic arches, panelled with carved wood, painted azure blue, and diapered with golden fleur de lis; underfoot, a pavement in alternate squares of black and white. A few paces off is an enormous pillar, and anotherseven in all down the length of the hall, supporing in the centre line the springing arches of the double groining. Around the first four pillars are stalls all glittering with glassware and trinkets, and around the last three are oaken benches, worn smooth and shining by the breeches of the litigants and the gowns of the attorneys. Ranged along the lofty walls, between the doors, between the windows, between the pillars, is the interminable series of statues of the rulers of France from Pharamond downward; the Rois fainants, with drooping eyes and indolent hanging arms; the valiant warrior kings, with head and hands boldly uplifted in the sight of heaven. The tall, pointed windows glow in a thousand colours; at the wide entrances to the Hall are richly carved doors; and the wholeroof, pillars, walls, cornices, doors, statuesis resplendent from top to bottom in a coating of blue and gold, already somewhat tarnished at the period of which we write, but which had almost entirely disappeared under dust and cobwebs in the year of grace 1549, when Du Breuil alluded to it in terms of admiration, but from hearsay only. -Now let the reader picture to himself that immense, oblong Hall under the wan light of a January morning and invaded by a motley, noisy crowd, pouring along the walls and eddying round the pillars, and he will have some idea of the scene as a whole, the peculiarities of which we will presently endeavour to describe more in detail. -Assuredly if Ravaillac had not assassinated Henri IV there would have been no documents relating to his trial to be deposited in the Record office of the Palais de Justice; no accomplices interested in causing those documents to disappear, and consequently no incendiaries compelled, in default of a better expedient, to set fire to the Record office in order to destroy the documents, and to burn down the Palais de Justice in order to burn the Record officein short, no conflagration of 1618. The old Palais would still be standing with its great Hall, and I could say to the reader Go and see for yourself, and we should both be exempt of the necessity, I of writing, he of reading this description, such as it is. All of which goes to prove the novel truth, that great events have incalculable consequences. -To be sure, it is quite possible that Ravaillac had no accomplices, also that, even if he had, they were in no way accessory to the fire of 1618. There exist two other highly plausible explanations. In the first place, the great fiery star a foot wide and an ell high, which, as every mothers son knows, fell from heaven on to the Palais on the 7th of March just after midnight; and secondly, Thophiles quatrain, which runs: -Certes, ce fut un triste jeu -Quand Paris dame Justice, -Pour avoir mang trop dpice -Se mit tout le palais en feu.3 -Whatever one may think of this triple explanationpolitical, physical, and poeticalof the burning of the Palais de Justice in 1618, about one fact there is unfortunately no doubt, and that is the fire itself. -Thanks to this disaster, and more still to the successive restorations which destroyed what the fire had spared, very little remains of this first residence of the Kings of France, of this original palace of the Louvre, so old even in the time of Philip the Fair, that in it they sought for traces of the magnificent buildings erected by King Robert and described by Helgaldus. Nearly all has gone. What has become of the Chancery Chamber in which St. Louis consummated his marriage? what of the garden where he administered justice, clad in a jerkin of camlet, a surcoat of coarse woollen stuff without sleeves, and over all a mantle of black sandal, and reclining on a carpet with Joinville? Where is the chamber of the Emperor Sigismund? where that of Charles IV? that of John Lackland? Where is the flight of steps from which Charles VI proclaimed his Edict of Pardon? the flag-stone whereon, in the presence of the Dauphin, Marcel strangled Robert de Clermont and the Marshal de Champange? the wicket where the bulls of the anti-Pope Benedict were torn up, and through which the bearers of them marched out, mitred and coped in mock state, to publicly make the amende honorable through the streets of Paris? and the great Hall with its blue and gold, its Gothic windows, its statues, its pillars, its immense vaulted roof so profusely carved and the gilded chamberand the stone lion kneeling at the door with head abased and tail between its legs, like the lions of Solomons throne, in that attitude of humility which beseems Strength in the presence of Justice? and the beautiful doors, and the gorgeous-hued windows, and the wrought iron-work which discouraged Biscornetteand the delicate cabinet-work of Du Hancy? How has time, how has man, served these marvels? What have they given us in exchange for all this, for this great page of Gallic history, for all this Gothic art? The uncouth, surbased arches of M. de Brosse, the clumsy architect of the great door of Saint-Gervais so much for art; and as regards history, we have the gossipy memoirs of the Great Pillar, which still resounds with the old wives tales of such men as Patru. -Well, that is not much to boast of. Let us return to the real great Hall of the real old Palais. -The two extremities of this huge parallelogram were occupied, the one by the famous marble table, so long, so broad, and so thick that, say the old territorial records in a style that would whet the appetite of a Gargantua, Never was such a slab of marble seen in the world; the other by the chapel in which Louis XI caused his statue to be sculptured kneeling in front of the Virgin, and to which he had transferredindifferent to the fact that thereby two niches were empty in the line of royal statuesthose of Charlemagne and Saint-Louis: two saints who, as Kings of France, he supposed to be high in favour in heaven. This chapel, which was still quite new, having been built scarcely six years, was carried out entirely in that charming style of delicate architecture, with its marvellous stone-work, its bold and exquisite tracery, which marks in France the end of the Gothic period, and lasts on into the middle of the sixteenth century in the ethereal fantasies of the Renaissance. The little fretted stone rose-window above the door was in particular a master-piece of grace and lightnessa star of lace. -In the centre of the Hall, opposite the great entrance, they had erected for the convenience of the Flemish envoys and other great personages invited to witness the performance of the Mystery, a raised platform covered with gold brocade and fixed against the wall, to which a special entrance had been contrived by utilizing a window into the passage from the Gilded Chamber. -According to custom, the performance was to take place upon the marble table, which had been prepared for that purpose since the morning. On the magnificent slab, all scored by the heels of the law-clerks, stood a high wooden erection, the upper floor of which, visible from every part of the Hall, was to serve as the stage, while its interior, hung round with draperies, furnished a dressing-room for the actors. A ladder, frankly placed in full view of the audience, formed the connecting link between stage and dressing-room, and served the double office of entrance and exit. There was no character however unexpected, no change of scene, no stage effect, but was obliged to clamber up this ladder. Dear and guileless infancy of art and of stage machinery! -Four sergeants of the provost of the Palaisthe appointed superintendents of all popular holidays, whether festivals or executionsstood on duty at the four corners of the marble table. -The piece was not to commence till the last stroke of noon of the great clock of the Palais. To be sure, this was very late for a theatrical performance; but they had been obliged to suit the convenience of the ambassadors. -Now, all this multitude had been waiting since the early morning; indeed, a considerable number of these worthy spectators had stood shivering and chattering their teeth with cold since break of day before the grand stair-case of the Palais; some even declared that they had spent the night in front of the great entrance to make sure of being the first to get in. The crowd became denser every moment, and like water that overflows its boundaries, began to mount the walls, to surge round the pillars, to rise up and cover the cornices, the window-sills, every projection and every coign of vantage in architecture or sculpture. The all-prevailing impatience, discomfort, and weariness, the license of a holiday approvedly dedicated to folly, the quarrels incessantly arising out of a sharp elbow or an iron-shod heel, the fatigue of long waiting all conduced to give a tone of bitterness and acerbity to the clamour of this closely packed, squeezed, hustled, stifled throng long before the hour at which the ambassadors were expected. Nothing was to be heard but grumbling and imprecations against the Flemings, the Cardinal de Bourbon, the Chief Magistrate, Madame Marguerite of Austria, the beadles, the cold, the heat, the bad weather, the Bishop of Paris, the Fools Pope, the pillars, the statues, this closed door, yonder open windowto the huge diversion of the bands of scholars and lackeys distributed through the crowd, who mingled their gibes and pranks with this seething mass of dissatisfaction, aggravating the general ill-humour by perpetual pin-pricks. -There was one group in particular of these joyous young demons who, after knocking out the glass of a window, had boldly seated themselves in the frame, from whence they could cast their gaze and their banter by turns at the crowd inside the Hall and that outside in the Place. By their aping gestures, their yells of laughter, by their loud interchange of opprobrious epithets with comrades at the other side of the Hall, it was very evident that these budding literati by no means shared the boredom and fatigue of the rest of the gathering, and that they knew very well how to extract out of the scene actually before them sufficient entertainment of their own to enable them to wait patiently for the other. -Why, by my soul, tis Joannes Frollo de Molendino! cried one of them to a little fair-haired imp with a handsome mischievous face, who had swarmed up the pillar and was clinging to the foliage of its capital; well are you named Jehan of the Mill, for your two arms and legs are just like the sails of a wind-mill. How long have you been here? -By the grace of the devil, returned Joannes Frollo, over four hours, and I sincerely trust they may be deducted from my time in purgatory. I heard the eight chanters of the King of Sicily start High Mass at seven in the Sainte-Chapelle. -Fine chanters forsooth! exclaimed the other, their voices are sharper than the peaks of their caps! The King had done better, before founding a Mass in honour of M. Saint-John, to inquire if M. Saint-John was fond of hearing Latin droned with a Proven?al accent. -And was it just for the sake of employing these rascally chanters of the King of Sicily that he did that? cried an old woman bitterly in the crowd beneath the window. I ask youa thousand livres parisis4 for a Mass, and that too to be charged on the license for selling salt-water fish in the fish-market of Paris. -Peace! old woman, replied a portly and solemn personage, who was holding his nose as he stood beside the fishwife; a Mass had to be founded. Would you have the King fall sick again? -Bravely said, Sir Gilles Lecornu,5 master furrier to the royal wardrobe! cried the little scholar clinging to the capital. -A burst of laughter from the whole band of scholars greeted the unfortunate name of the hapless Court furrier. -Lecornu! Gilles Lecornu! shouted some. -Cornitus et hirsutus! 6 responded another. -Why, of course, continued the little wretch on the capital. But what is there to laugh about? A worthy man is Gilles Lecornu, brother to Master Jehan Lecornu, provost of the Royal Palais, son of Master Mahiet Lecornu, head keeper of the Forest of Vincennes, all good citizens of Paris, married every one of them from father to son! -The mirth redoubled. The portly furrier answered never a word, but did his best to escape the attention directed to him from all sides; but he puffed and panted in vain. Like a wedge being driven into wood, his struggles only served to fix his broad apoplectic face, purple with anger and vexation, more firmly between the shoulders of his neighbours. -At last one of these neighbours, fat, pursy, and worthy as himself, came to his aid. -Out upon these graceless scholars who dare to address a burgher in such a manner! In my day they would have first been beaten with sticks, and then burnt on them. -This set the whole band agog. -Hol! h! what tunes this? Whos that old bird of ill omen? -Oh, I know him! exclaimed one; its Ma?tre Andry Musnier. -Yes, hes one of the four booksellers by appointment to the University, said another. -Everything goes by fours in that shop! cried a third. Four nations, four faculties, four holidays, four procurators, four electors, four booksellers. -Very good, returned Jehan Frollo, well quadruple the devil for them. -Musnier, well burn thy books. -Musnier, well beat thy servants. -Musnier, well tickle thy wife. -The good, plump Mlle. Oudarde. -Who is as buxom and merry as if she were already a widow. -The devil fly away with you all, growled Ma?tre Andry Musnier. -Ma?tre Andry, said Jehan, still hanging fast to his capital, hold thy tongue, or I fall plump on thy head. -Ma?tre Andry looked up, appeared to calculate for a moment the height of the pillar and the weight of the little rascal, mentally multiplied that weight by the square of the velocity and held his peace. Whereupon Jehan, left master of the field, added triumphantly, And Id do it too, though I am the brother of an archdeacon. -A fine set of gentlemen those of ours at the University, not even on a day like this do they see that we get our rights. Theres a may-pole and a bonfire in the town, a Fools Pope and Flemish ambassadors in the city, but at the University, nothing! -And yet the Place Maubert is large enough, observed one of the youngsters, ensconced in a corner of the window-ledge. -Down with the Rector, the electors, and the procurators! yelled Jehan. -Well make a bonfire to-night in the Champs-Gaillard with Ma?tre Andrys books! added another. -And the desks of the scribes! cried his neighbour. -And the wands of the beadles! -And the spittoons of the deans! -And the muniment chests of the procurators! -And the tubs of the doctors! -And the stools of the Rector! -Down! bellowed little Jehan in a roaring bass; down with Ma?tre Andry, the beadles and the scribes; down with the theologians, the physicians, and the priests; down with the procurators, the electors, and the Rector!! - Tis the end of the world! muttered Ma?tre Andry, stopping his ears. -Talk of the Rectorthere he goes down the square! cried one of those in the window. And they all strained to catch a glimpse. -Is it in truth our venerable Rector, Ma?tre Thibaut? inquired Jehan Frollo du Moulin, who from his pillar in the interior of the Hall could see nothing of what went on outside. -Yes, yes, responded the others in chorus, it is Ma?tre Thibaut, the Rector himself. -It was in fact the Rector, accompanied by all the dignitaries of the University going in procession to receive the ambassadors, and in the act of crossing the Place du Palais. The scholars crowding at the window greeted them as they passed with gibes and ironical plaudits. The Rector marching at the head of his band received the first volleyit was a heavy one. -Good-day, Monsieur the RectorHol there! Good-day to you! -How comes it that the old gambler has managed to be here? Has he then actually left his dice? -Look at him jogging alone on his muleits ears are not as long as his own! -Hol, good-day to you Monsieur the Rector Thibaut! Tybalde aleator!7 old numskull! old gamester! -God save you! How often did you throw double six last night? -Oh, just look at the lantern-jawed old face of himall livid and drawn and battered from his love of dice and gaming! -Where are you off to like that, Thibaut, Tybalde ad dados, 8 turning your back on the University and trotting towards the town? -Doubtless he is going to seek a lodging in the Rue Thibautod! 9 cried Jehan Frollo. -The whole ribald crew repeated the pun in a voice of thunder and with furious clapping of hands. -You are off to seek a lodging in the Rue Thibautod, arent you, Monsieur the Rector, own partner to the devil! -Now came the turn of the other dignitaries. -Down with the beadles! Down with the mace-bearers! -Tell me, Robin Poussepain, who is that one over there? -It is Gilbert de Suilly, Gilbertus de Soliaco, the Chancellor of the College of Autun. -Here, take my shoeyou have a better place than I havethrow it in his face! -Saturnalitias mittimus ecce nuces! 10 -Down with the six theologians in their white surplices! -Are those the theologians? I took them for the six white geese Sainte-Genevive pays to the Town as tribute for the fief of Roogny. -Down with the physicians! -Down with all the pompous and squabbling disputations! -Here goes my cap at thy head, Chancellor of Sainte-Genevive; I owe thee a grudge. He gave my place in the Nation of Normandy to little Ascaino Falzaspada, who as an Italian, belongs of right to the Province of Bourges. - Tis an injustice! cried the scholars in chorus. Down with the Chancellor of Sainte-Genevive! -Ho, there, Ma?tre Joachim de Ladehors! Ho, Louis Dahuille! Ho, Lambert Hoctement! -The devil choke the Procurator of the Nation of Germany! -And the chaplains of the Sainte-Chapelle in their gray amices; cum tunicis grisis! -Seu de pellibus grisis fourratis! -There go the Masters of Art! Oh, the fine red copes! and oh, the fine black ones! -That makes a fine tail for the Rector! -He might be the Doge of Venice going to espouse the sea. -Look, Jehan, the canons of Sainte-Genevive! -The foul fiend take the whole lot of them! -Abb Claude Choart! Doctor Claude Choart, do you seek Marie la Giffarde? -Youll find her in the Rue Glatigny. -Bed-making for the King of the Bawdies! -She pays her fourpencequatuor denarios. -Aut unum bombum. -Would you have her pay you with one on the nose? -Comrades! Ma?tre Simon Sanguin, the elector of the Nation of Picardy, with his wife on the saddle behind him. -Post equitem sedet atra cura. 11 -Good-day to you, Monsieur the Elector! -Good-night to you, Madame the Electress! -Lucky dogs to be able to see all that! sighed Joannes de Molendino, still perched among the acanthus leaves of his capital. -Meanwhile the bookseller of the University, Ma?tre Andry Musnier, leaned over and whispered to the Court furrier, Ma?tre Gilles Lecornu: -I tell you, monsieur, tis the end of the world. Never has there been such unbridled license among the scholars. It all comes of these accursed inventionsthey ruin everythingthe artillery, the culverine, the blunderbuss, and above all, printing, that second pestilence brought us from Germany. No more manuscriptsno more books! Printing gives the death-blow to bookselling. It is the beginning of the end. -I, too, am well aware of it by the increasing preference for velvet stuffs, said the furrier. -At that moment it struck twelve. -A long-drawn Ah! went up from the crowd. -The scholars held their peace. There ensued a general stir and upheaval, a great shuffling of feet and movement of heads, much coughing and blowing of noses; everyone resettled himself, rose on tip-toe, placed himself in the most favourable position obtainable. Then deep silence, every neck outstretched, every mouth agape, every eye fixed on the marble table. Nothing appeared; only the four sergeants were still at their posts, stiff and motionless as four painted statues. Next, all eyes turned towards the platform reserved for the Flemish envoys. The door remained closed and the platform empty. Since daybreak the multitude had been waiting for three thingsthe hour of noon, the Flemish ambassadors, and the Mystery-Play. Noon alone had kept the appointment. It was too bad. They waited one, two, three, five minutesa quarter of an hournothing happened. Then anger followed on the heels of impatience; indignant words flew hither and thither, though in suppressed tones as yet. The Mystery, the Mystery! they murmured sullenly. The temper of the crowd began to rise rapidly. The warning growls of the gathering storm rumbled overhead. It was Jehan du Moulin who struck out the first flash. -Lets have the Mystery, and the devil take the Flemings! he cried at the pitch of his voice, coiling himself about his pillar like a serpent. -The multitude clapped its approval. -The Mystery, the Mystery! they repeated, and to the devil with all Flanders! -Give us the Mystery at once, continued the scholar, or its my advice we hang the provost of the Palais by way of both Comedy and Morality. -Well said! shouted the crowd, and lets begin the hanging by stringing up his sergeants. -A great roar of applause followed. The four poor devils grew pale and glanced apprehensively at one another. The multitude surged towards them, and they already saw the frail wooden balustrade that formed the only barrier between them and the crowd bulge and give way under the pressure from without. -The moment was critical. -At them! At them! came from all sides. -At that instant the curtain of the dressing-room we have described was raised to give passage to a personage, the mere sight of whom suddenly arrested the crowd, and, as if by magic, transformed its anger into curiosity. -Silence! Silence! -But slightly reassured and trembling in every limb, the person in question advanced to the edge of the marble table with a profusion of bows which, the nearer he approached, assumed more and more the character of genuflections. -By this time quiet had been gradually restored, and there only remained that faint hum which always rises out of the silence of a great crowd. -Messieurs the bourgeois, he began, and Mesdemoiselles the bourgeoises, we shall have the honour of declaiming and performing before his Eminence Monsieur the Cardinal a very fine Morality entitled The Good Judgment of Our Lady the Virgin Mary. I play Jupiter. His Eminence accompanies at this moment the most honourable Embassy of the Duke of Austria, just now engaged in listening to the harangue of Monsieur the Rector of the University at the Porte Baudets. As soon as the Most Reverend the Cardinal arrives we will commence. -Certainly nothing less than the direct intervention of Jupiter could have saved the four unhappy sergeants of the provost of the Palais from destruction. Were we so fortunate as to have invented this most veracious history and were therefore liable to be called to task for it by Our Lady of Criticism, not against us could the classical rule be cited, Nec deus intersit. -For the rest, the costume of Seigneur Jupiter was very fine, and had contributed not a little towards soothing the crowd by occupying its whole attention. Jupiter was arrayed in a brigandine or shirt of mail of black velvet thickly studded with gilt nails, on his head was a helmet embellished with silver-gilt buttons, and but for the rouge and the great beard which covered respectively the upper and lower half of his face, but for the roll of gilded pasteboard in his hand studded with iron spikes and bristling with jagged strips of tinsel, which experienced eyes at once recognised as the dread thunder-bolt, and were it not for his flesh-coloured feet, sandalled and beribboned la Grecque, you would have been very apt to mistake him for one of M. de Berrys company of Breton archers. -_____________________ -1 Notre-Dame de Paris was begun July 30, 1830. -2 The term Gothic used in its customary sense is quite incorrect, but is hallowed by tradition. We accept it, therefore, and use it like the rest of the world, to characterize the architecture of the latter half of the Middle Ages, of which the pointed arch forms the central idea, and which succeeds the architecture of the first period, of which the round arch is the prevailing feature.Authors Note. -3 -In truth it was a sorry game -When in Paris Dame Justice, -Having gorged herself with spice, -Set all her palace in a flame -. -The application of these lines depends, unfortunately, on an untranslatable play on the word pice, which means both spice and lawyers fees. -4 Old French money was reckoned according to two standards, that of Paris (parisis) and Tours (tournois); the livre parisis, the old franc, having twenty-five sols or sous, and the livre tournois twenty sols.Translators Note. -5 Cuckold. -6 Horned and hairy. -7 Thibaut, thou gamester. -8 Thibaut towards losses. -9 A pun. Thibaut aux ds; i. e., Thibaut with the dice. -10 Freely translated: Therell be rotten apples thrown at heads to-day. -11 Behind the rider sits black care. -Chapter 2 - Pierre Gringoire -Unfortunately, the admiration and satisfaction so universally excited by his costume died out during his harangue, and when he reached the unlucky concluding words, As soon as his Reverence the Cardinal arrives, we will begin, his voice was drowned in a tempest of hooting. -Begin on the spot! The Mystery, the Mystery at once! shouted the audience, the shrill voice of Joannes de Molendino sounding above all the rest, and piercing the general uproar like the fife in a charivari at N?mes. -Begin! piped the boy. -Down with Jupiter and the Cardinal de Bourbon! yelled Robin Poussepain and the other scholars perched on the window-sill. -The Morality! roared the crowd. At onceon the spot. The sack and the rope for the players and the Cardinal! -Poor Jupiter, quaking, bewildered, pale beneath his rouge, dropped his thunder-bolt and took his helmet in his hand; then bowing and trembling: His Eminence, he stammered, the AmbassadorsMadame Marguerite of Flanders he could get no farther. Truth to tell, he was afraid of being hanged by the populace for beginning too late, hanged by the Cardinal for being too soon; on either side he beheld an abyssthat is to say, a gibbet. -Mercifully some one arrived upon the scene to extricate him from the dilemma and assume the responsibility. -An individual standing inside the balustrade in the space left clear round the marble table, and whom up till now no one had noticed, so effectually was his tall and spare figure concealed from view by the thickness of the pillar against which he leanedthis person, thin, sallow, light-haired, young still, though furrowed of brow and cheek, with gleaming eye and smiling mouth, clad in black serge threadbare and shiny with age, now approached the marble table and signed to the wretched victim. But the other was too perturbed to notice. -The newcomer advanced a step nearer. Jupiter, said he, my dear Jupiter. -The other heard nothing. -At last the tall young man losing patience, shouted almost in his face: Michel Giborne! -Who calls? said Jupiter, starting as if from a trance. -It is I, answered the stranger in black. -Ah! said Jupiter. -Begin at once, went on the other. Do you content the peopleI will undertake to appease Monsieur the provost, who, in his turn, will appease Monsieur the Cardinal. -Jupiter breathed again. -Messeigneurs the bourgeois, he shouted with all the force of his lungs to the audience, which had not ceased to hoot him, we are going to begin. -Evoe Jupiter! Plaudite cives!1 yelled the scholars. -No?l! No?l! shouted the people. -There was a deafening clapping of hands, and the Hall still rocked with plaudits after Jupiter had retired behind his curtain. -Meanwhile the unknown personage who had so magically transformed the storm into a calm, had modestly re-entered the penumbra of his pillar, where doubtless he would have remained, unseen, unheard, and motionless as before, had he not been lured out of it by two young women who, seated in the first row of spectators, had witnessed his colloquy with Michel GiborneJupiter. -Maitre, said one of them, beckoning to him to come nearer. -Hush, my dear Linarde, said her companion, a pretty, rosy-cheeked girl, courageous in the consciousness of her holiday finery, he doesnt belong to the Universityhes a layman. You mustnt say Ma?tre to him, you must say Messire. -Messire, resumed Linarde. -The stranger approached the balustrade. -What can I do for you, mesdemoiselles? he asked eagerly. -Oh, nothing! said Linarde, all confused; it is my neighbour, Gisquette la Gencienne, who wants to speak to you. -Not at all, said Gisquette, blushing, it was Linarde who called you Maitre, and I told her she ought to say Messire. -The two girls cast down their eyes. The stranger, nothing loath to start a conversation with them, looked at them smilingly. -So you have nothing to say to me, ladies? -Oh, nothing at all, Gisquette declared. -No, nothing, added Linarde. -The tall young man made as if to retire, but the two inquiring damsels were not inclined to let him go so soon. -Messire, began Gisquette with the impetuous haste of a woman taking a resolve, it appears you are acquainted with the soldier who is going to play the part of Madame the Virgin in the Mystery. -You mean the part of Jupiter, returned the unknown. -Yes, of course! said Linarde. Isnt she stupid? So you know Jupiter? -Michel Giborne? Yes, madame. -He has a splendid beard, said Linarde. -Will it be very fine what they are going to say? asked Gisquette shyly. -Extremely fine, mademoiselle, responded the unknown without the slightest hesitation. -What is it to be? asked Linarde. - The Good Judgment of Madame the Virgin, a Morality, an it please you, mademoiselle. -Ah! thats different, rejoined Linarde. -A short silence ensued. It was broken by the young man. -It is an entirely new Morality, said he, and has never been used before. -Then it is not the same as they gave two years ago on the day of the entry of Monsieur the Legate, in which there were three beautiful girls to represent certain personages -Sirens, said Linarde. -And quite naked, added the young man. -Linarde modestly cast down her eyes. Gisquette glanced at her and then followed her example. -It was a very pleasant sight, continued the young man, unabashed. But the Morality to-day was composed expressly for Madame the Lady of Flanders. -Will they sing any bergerettes? asked Gisquette. -Fie! exclaimed the unknown; love-songs in a Morality? The different sorts of plays must not be confounded. Now, if it were sotie,2 well and good -What a pity! returned Gisquette. That day at the Ponceau fountain there were wild men and women who fought with one another and formed themselves into different groups, singing little airs and love-songs. -What is suitable for a legate, remarked the unknown dryly, would not be seemly for a princess. -And close by, Linarde went on, a number of deep-toned instruments played some wonderful melodies. -And for the refreshment of the passer-by, added Gisquette, the fountains spouted wine and milk and hypocras from three mouths, and every one drank that would. -And a little below the Ponceau fountain at the Trinit, continued Linarde, there was a Passion Play acted without words. -Yes, so there was! cried Gisquette. Our Lord on the cross and the two thieves to right and left of him. -Here the two friends, warming to the recollection of the legates entry, both began talking at once. And farther on, at the Porte-aux-Peintres, were other persons very richly dressed. -And at the Fountain of the Holy Innocents, that huntsman pursuing a him with great barking of dogs and blowing of horns. -And near the slaughter-house of Paris, that wooden erection representing the fortress of Dieppe. -And you remember, Gisquette, just as the legate passed they sounded the assault, and all the English had their throats cut. -And near the Chatelet Gate were some very fine figures. -And on the Pont-au-Change, too, which was all hung with draperies. -And when the legate passed over it they let fly more than two hundred dozen birds of all kinds. That was beautiful, Linarde! -It will be far finer to-day, broke in their interlocutor at last, who had listened to them with evident impatience. -You can promise us that this Mystery will be a fine one? said Gisquette. -Most assuredly I can, he replied; then added with a certain solemnity, Mesdemoiselles, I am myself the author of it. -Truly? exclaimed the girls in amazement. -Yes, truly, asserted the poet with conscious pride. That is to say, there are two of usJehan Marchand, who sawed the planks and put up the wooden structure of the theatre, and I, who wrote the piece. My name is Pierre Gringoire. -Not with greater pride could the author of the Cid have said, I am Pierre Corneille. -Our readers cannot have failed to note that some time had elapsed between the moment at which Jupiter withdrew behind the curtain, and that at which the author thus abruptly revealed himself to the unsophisticated admiration of Gisquette and Linarde. Strange to say, all this crowd, so tumultuous but a few minutes ago, were now waiting patiently with implicit faith in the players word. A proof of the everlasting truth still demonstrated in our theatres, that the best means of making the public wait patiently is to assure them that the performance is about to begin. -However, the scholar Joannes was not so easily lulled. Hol! he shouted suddenly into the midst of the peaceful expectation which had succeeded the uproar, Jupiter! Madame the Virgin! Ye devils mountebanks! would you mock us? The piece! the piece. Do you begin this moment, or we will -This was enough. Immediately a sound of music from high- and low-pitched instruments was heard underneath the structure, the curtain was raised, four party-coloured and painted figures issued from it, and clambering up the steep ladder on to the upper platform, ranged themselves in a row fronting the audience, whom they greeted with a profound obeisance. The symphony then ceased. The Mystery began. -After receiving ample meed of applause in return for their bows, the four characters proceeded, amid profound silence, to deliver a prologue which we willingly spare the reader. Besides, just as in our own day, the public was far more interested in the costumes the actors wore than the parts they enactedand therein they chose the better part. -All four were attired in party-coloured robes, half yellow, half white, differing from one another only in material; the first being of gold and silver brocade, the second of silk, the third of woollen stuff, the fourth of linen. The first of these figures carried a sword in his right hand, the second two golden keys, the third a pair of scales, the fourth a spade; and for the benefit of such sluggish capacities as might have failed to penetrate the transparency of these attributes, on the hem of the brocade robe was embroidered in enormous black letters, I am Nobility, on the silk one I am Clergy, on the woollen one I am Commerce, on the linen one I am Labour. The sex of the two male allegories was plainly indicated by the comparative shortness of their tunics and their Phrygian caps, whereas the female characters wore robes of ample length and hoods on their heads. -It would also have required real perverseness not to have understood from the poetic imagery of the prologue that Labour was espoused to Commerce, and Clergy to Nobility, and that the two happy couples possessed between them a magnificent golden dolphin (dauphin) which they proposed to adjudge only to the most beautiful damsel. Accordingly, they were roaming the world in search of this Fair One, and, after rejecting successively the Queen of Golconda, the Princess of Trebizonde, the daughter of the Grand Khan of Tartary, etc, etc., Labour and Commerce, Clergy and Nobility, had come to rest themselves awhile on the marble table of the Palais de Justice, and to deliver themselves before an honoured audience of a multitude of sententious phrases, moral maxims, sophisms, flowers of speech, as were freely dispensed in those days by the Faculty of Arts or at the examinations at which the Masters took their degree. -All this was, in effect, very fine. -Meanwhile, in all that crowd over which the four allegorical figures were pouring out floods of metaphor, no ear was more attentive, no heart more palpitating, no eye more eager, no neck more outstretched than the eye, the ear, the heart, the neck of the poet-author, our good Pierre Gringoire, who but a little while before had been unable to resist the joy of revealing his name to a couple of pretty girls. He had retired again behind his pillar, a few paces from them, where he stood gazing, listening, relishing. The favourable applause which had greeted the opening of his prologue was still thrilling through his vitals; and he was completely carried away by that kind of contemplative ecstasy with which the dramatic author follows his ideas as they drop one by one from the lips of the actor amid the silence of a vast audience. Happy Pierre Gringoire! -Sad to say, however, this first ecstasy was but of short duration. Scarcely had Gringoire raised this intoxicating cup of triumph and delight to his lips than a drop of bitterness came to mingle with it. -A beggar, a shocking tatterdemalion, too tightly squeezed in among the crowd to be able to collect his usual harvest, or, in all probability, had not found sufficient to indemnify himself in the pockets of his immediate neighbours, had conceived the bright idea of perching himself in some conspicuous spot from whence he might attract the gaze and the alms of the benevolent. To this end, during the opening lines of the prologue, he had managed to hoist himself up by the pillars of the reserved platform on to the cornice which projected around the foot of its balustrade, where he seated himself, soliciting the attention and the pity of the throng by his rags and a hideous sore covering his right arm. He did not, however, utter a word. -The silence he preserved allowed of the prologue proceeding without let or hindrance, nor would any noticeable disturbance have occurred if, as luck would have it, the scholar Jehan had not, from his own high perch, espied the beggar and his antics. A wild fit of laughter seized the graceless young rascal, and, unconcerned at interrupting the performance and distracting the attention of the audience, he cried delightedly: -Oh, look at that old fraud over there begging! -Any one who has ever thrown a stone into a frog-pond, or fired into a covey of birds, will have some idea of the effect of these incongruous words breaking in upon the all-pervading quiet. Gringoire started as if he had received an electric shock. The prologue broke off short, and all heads turned suddenly towards the beggar, who, far from being disconcerted, only saw in this incident an excellent opportunity for gathering a harvest, and at once began whining in a piteous voice with half-closed eyes: Charity, I pray you! -Why, upon my soul! cried Jehan, if it isnt Clopin Trouillefou! Hol! friend, so thy sore was troublesome on thy leg that thou hast removed it to thine arm? and so saying, with the dexterity of a monkey he tossed a small silver piece into the greasy old beaver which the beggar held out with his diseased arm. The man received both alms and sarcasm without wincing, and resumed his doleful petition: Charity, I pray you! -This episode had distracted the audience not a little, and a good many of the spectators, Robin Poussepain and the rest of the students at the head, delightedly applauded this absurd duet improvised in the middle of the prologue between the scholar with his shrill, piping voice, and the beggar with his imperturbable whine. -Gringoire was seriously put out. Recovering from his first stupefaction, he pulled himself together hurriedly and shouted to the four actors on the stage: Go on! que diable! go on! without deigning even a glance of reprobation at the two brawlers. -At that moment he felt a pluck at the edge of his surcoat, and turning round, not in the best of humours, he forced an unwilling smile to his lips, for it was the pretty hand of Gisquette la Gencienne thrust through the balustrade and thus soliciting his attention. -Monsieur, said the girl, are they going on? -To be sure, Gringoire replied, half offended by the question. -In that case, messire, she continued, will you of your courtesy explain to me -What they are going to say? broke in Gringoire. Well, listen. -No, said Gisquette; but what they have already said. -Gringoire started violently like a man touched in an open wound. A pestilence on the witless little dunce! he muttered between his teeth; and from that moment Gisquette was utterly lost in his estimation. -Meanwhile the actors had obeyed his injunction, and the public, seeing that they were beginning to speak, resettled itself to listen; not, however, without having lost many a beautiful phrase in the soldering of the two parts of the piece which had so abruptly been cut asunder. Gringoire reflected bitterly on this fact. However, tranquility had gradually been restored, Jehan was silent, the beggar was counting the small change in his hat, and the play had once more got the upper hand. -Sooth to say, it was a very fine work which, it seems to us, might well be turned to account even now with a few modifications. The exposition, perhaps somewhat lengthy and dry, but strictly according to prescribed rules, was simple, and Gringoire, in the inner sanctuary of his judgment, frankly admired its perspicuity. -As one might very well suppose, the four allegorical personages were somewhat fatigued after having travelled over three parts of the globe without finding an opportunity of disposing suitably of their golden dolphin. Thereupon, a long eulogy on the marvellous fish, with a thousand delicate allusions to the young betrothed of Marguerite of Flanders who at that moment was languishing in dismal seclusion at Amboise, entirely unaware that Labour and Clergy, Nobility and Commerce, had just made the tour of the world on his behalf. The said dolphin, then, was handsome, was young, was brave; above all (splendid origin of all the royal virtues) he was the son of the Lion of France. Now I maintain that this bold metaphor is admirable, and the natural history of the stage has no occasion on a day of allegory and royal epithalamium to take exception at a dolphin who is son to a lion. These rare and Pindaric combinations merely prove the poets enthusiasm. Nevertheless, in justice to fair criticism be it said, the poet might have developed this beautiful idea in less than two hundred lines. On the other hand, by the arrangements of Monsieur the Provost, the Mystery was to last from noon till four oclock, and they were obliged to say something. Besides, the people listened very patiently. -Suddenly, in the very middle of a quarrel between Dame Commerce and my Lady Nobility, and just as Labour was pronouncing this wonderful line: -Beast more triumphant neer in woods Ive seen, -the door of the reserved platform which up till then had remained inopportunely closed, now opened still more inopportunely, and the stentorian voice of the usher announced His Eminence Monseigneur the Cardinal de Bourbon! -_____________________ -1 Hail, Jupiter! Citizens, applaud! -2 A satirical play very much in vogue during the fifteenth and sixteenth centuries. -Chapter 3 - The Cardinal -Alas, poor Gringoire! The noise of the double petards let off on Saint-Johns Day, a salvo of twenty arquebuses, the thunder of the famous culverin of the Tour de Billy, which on September 29, 1465, during the siege of Paris, killed seven Burgundians at a blow, the explosion of the whole stock of gunpowder stored at the Temple Gate would have assailed his ears less rudely at this solemn and dramatic moment than those few words from the lips of the usher: His Eminence the Cardinal de Bourbon! -Not that Pierre Gringoire either feared the Cardinal or despised him; he was neither so weak nor so presumptuous. A true eclectic, as nowadays he would be called, Gringoire was of those firm and elevated spirits, moderate and calm, who ever maintain an even balanceStare in dimidio rerum and who are full of sense and liberal philosophy, to whom Wisdom, like another Ariadne, seems to have given a ball of thread which they have gone on unwinding since the beginning of all things through the labyrinthine paths of human affairs. One comes upon them in all ages and ever the same; that is to say, ever conforming to the times. And without counting our Pierre Gringoire, who would represent them in the fifteenth century if we could succeed in conferring on him the distinction he merits, it was certainly their spirit which inspired Father de Bruel in the sixteenth century, when he wrote the following sublimely na?ve words, worthy of all ages: I am Parisian by nation, and parrhisian by speech, since parrhisia in Greek signifies freedom of speech, which freedom I have used even towards Messeigneurs the Cardinals, uncle and brother to Monseigneur the Prince de Conty: albeit with due respect for their high degree and without offending any one of their train, which is saying much. -There was therefore neither dislike of the Cardinal nor contemptuous indifference to his presence in the unpleasing impression made on Gringoire. Quite the contrary; for our poet had too much common sense and too threadbare a doublet not to attach particular value to the fact that many an allusion in his prologue, and more especially the glorification of the dolphin, son of the Lion of France, would fall upon the ear of an Eminentissime. But self-interest is not the predominating quality in the noble nature of the poet. Supposing the entity of the poet to be expressed by the number ten, it is certain that a chemist in analyzing and pharmacopoeizing it, as Rabelais terms it, would find it to be composed of one part self-interest to nine parts of self-esteem. -Now, at the moment when the door opened for the Cardinals entry, Gringoires nine parts of self-esteem, swollen and inflated by the breath of popular admiration, were in a state of prodigious enlargement, obliterating that almost imperceptible molecule of self-interest which we just now pointed out as a component part of the poets constitutiona priceless ingredient, be it said, the ballast of common sense and humanity, without which they would forever wander in the clouds. Gringoire was revelling in the delights of seeing, of, so to speak, touching, an entire assemblage (common folk, it is true, but what of that?) stunned, petrified, suffocated almost by the inexhaustible flow of words which poured down upon them from every point of his epithalamium. I affirm that he shared in the general beatitude, and that, unlike La Fontaine, who, at the performance of his comedy Florentin, inquired, What bungler wrote this balderdash? Gringoire would gladly have asked his neighbours, Who is the author of this master-piece? Judge, therefore, of the effect produced on him by the abrupt and ill-timed arrival of the Cardinal. -And his worst fears were but too fully realized. The entry of his Eminence set the whole audience in commotion. Every head was turned towards the gallery. You could not hear yourself speak. The Cardinal! The Cardinal! resounded from every mouth. For the second time the unfortunate prologue came to an abrupt stop. -The Cardinal halted for a moment on the threshold of the platform, and while he cast a glance of indifference over the crowd the uproar increased. Each one wanted a good view, and strained to raise his head above his neighbours. -And in truth he was a very exalted personage, the sight of whom was worth any amount of Mysteries. Charles, Cardinal de Bourbon, Archbishop and Count of Lyons, Primate of all Gaul, was related to Louis XI through his brother, Pierre, Lord of Beaujeu, who had married the Kings eldest daughter, and to Charles the Bold through his mother, Agnes of Burgundy. The dominant trait, the prevailing and most striking feature in the character of the Primate of Gaul, was his courtier spirit and unswerving devotion to the powers that be. One may imagine the innumerable perplexities in which these two relationships involved him, and through what temporal shoals he had to steer his spiritual bark in order to avoid being wrecked either on Louis or on Charles, that Scylla and Charybdis which had swallowed up both the Duke of Nemours and the Constable of Saint-Pol. Heaven be praised, however, he had managed the voyage well, and had come safely to anchor in Rome without mishap. Yet, although he was in port, and precisely because he was in port, he never recalled without a qualm of uneasiness the many changes and chances of his long and stormy political voyage, and he often said that the year 1476 had been for him both black and white; meaning that in that year he had lost his mother, the Duchess of Bourbonnais, and his cousin, the Duke of Burgundy, and that the one death had consoled him for the other. -For the rest, he was a proper gentleman; led the pleasant life befitting a cardinal, was ever willing to make merry on the royal vintage of Chaillot, had no objection to Richarde de la Garmoise and Thomasse la Saillarde, would rather give alms to a pretty girl than an old woman, for all of which reasons he was high in favour with the populace of Paris. He was always surrounded by a little court of bishops and abbots of high degree, gay and sociable gentlemen, never averse to a thorough good dinner; and many a time had the pious gossips of Saint-Germain dAuxerre been scandalized in passing at night under the lighted windows of the H?tel de Bourbon, to hear the selfsame voices which erstwhile had chanted vespers for them now trolling out, to the jingle of glasses, the bacchanalian verses of Benedict XII (the Pope who added the third crown to the tiara) beginning Bibamus papaliter (Let us drink like Popes). -Without doubt it was this well-earned popularity which saved him from any demonstration of ill-will on the part of the crowd, so dissatisfied but a moment before, and but little disposed to evince respect towards a Cardinal on the very day they were going to elect a Pope of their own. But the Parisians bear very little malice; besides, having forced the performance to commence of their own authority, they had worsted the Cardinal, and their victory sufficed them. Moreover, Monseigneur was a handsome man, and he wore his handsome red robe excellently well; which is equivalent to saying that he had all the women, and consequently the greater part of the audience, on his side. Decidedly it would have shown great want both of justice and of good taste to hoot a Cardinal for coming late to the play, when he is a handsome man and wears his red robe with so handsome an air. -He entered then, greeted the audience with that smile which the great instinctively bestow upon the people, and slowly directed his steps towards his chair of scarlet velvet, his mind obviously preoccupied by some very different matter. His train, or what we should now call his staff, of bishops and abbots, streamed after him on to the platform, greatly increasing the disturbance and the curiosity down among the spectators. Each one was anxious to point them out or name them, to show that he knew at least one of them; some pointing to the Bishop of MarseillesAlaudet, if I remember right some to the Dean of Saint-Denis, others again to Robert de Lespinasse, Abbot of Saint-Germain-des-Prs, the dissolute brother of a mistress of Louis XI, all with much ribald laughter and scurrilous jesting. -As for the scholars, they swore like troopers. This was their own especial day, their Feast of Fools, their Saturnalia, the annual orgy of the Basoche1 and the Universityno turpitude, no foulness of language but was right and proper to that day. Besides, there was many a madcap light o love down in the crowd to spur them onSimone Quatrelivres, Agns la Gadine, Robine Pidebou. It was the least that could be expected, that they should be allowed to curse at their ease and blaspheme a little on so joyful an occasion and in such good companychurchmen and courtesans. Nor did they hesitate to take full advantage thereof, and into the midst of the all-prevailing hubbub there poured an appalling torrent of blasphemies and enormities of every description from these clerks and scholars, tongue-tied all the rest of the year through fear of the branding-iron of Saint-Louis. Poor Saint-Louis, they were snapping their fingers at him in his own Palais de Justice. Each one of them had singled out among the new arrivals some cassockblack or gray, white or violet Joannes Frollo de Molendino, as brother to an archdeacon, having audaciously assailed the red robe, fixing his bold eyes on the Cardinal and yelling at the pitch of his voice, Cappa repleta mero! Oh, cassock full of wine! -But all these details which we thus lay bare for the edification of the reader were so overborne by the general clamour that they failed altogether to reach the reserved platform. In any case the Cardinal would have taken but little heed of them, such license being entirely in keeping with the manners of the day. Besides, his mind was full of something else, as was evident by his preoccupied air; a cause of concern which followed close upon his heels and entered almost at the time with him on to the platform. This was the Flemish Embassy. -Not that he was a profound politician and thus concerned for the possible consequences of the marriage between his one cousin, Madame Marguerite of Burgundy, and his other cousin, the Dauphin Charles; little he cared how long the patched-up friendship between the Duke of Austria and the King of France would last, nor how the King of England would regard this slight offered to his daughter, and he drank freely each evening of the royal vintage of Chaillot, never dreaming that a few flagons of this same wine (somewhat revised and corrected, it is true), cordially presented to Edward IV by Louis XI, would serve one fine day to rid Louis XI of Edward IV. No, the most honourable Embassy of Monsieur the Duke of Austria brought none of these anxieties to the Cardinals mind; the annoyance came from another quarter. In truth, it was no small hardship, as we have already hinted at the beginning of this book, that he, Charles of Bourbon, should be forced to offer a courteous welcome and entertainment to a squad of unknown burghers; he, the Cardinal, receive mere sheriffs; he, the Frenchman, a polished bon-viveur, and these beer-drinking Flemish boorsand all this in public too! Faith, it was one of the most irksome parts he had ever had to play at the good pleasure of the King. -However, he had studied that part so well, that when the usher announced in sonorous tones, Messieurs, the Envoys of Monsieur the Duke of Austria, he turned towards the door with the most courteous grace in the world. Needless to say, every head in the Hall turned in the same direction. -Thereupon there entered, walking two and two, and with a gravity of demeanour which contrasted strongly with the flippant manner of the Cardinals ecclesiastical following, the forty-eight ambassadors of Maximilian of Austria, led by the Reverend Father in God, Jehan, Abbot of Saint-Bertin, Chancellor of the Golden Fleece, and Jacques de Goy, Sieur Dauby, baillie of Ghent. Deep silence fell upon the assemblage, only broken by suppressed titters at the uncouth names and bourgeois qualifications which each of these persons transmitted with imperturbable gravity to the usher, who proceeded to hurl name and title unrecognisably mixed and mutilated, at the crowd below. There was Master Loys Roelof, Sheriff of the City of Louvain; Messire Clays dEtuelde, Sheriff of Brussels; Messire Paul de Baeust, Sieur of Voirmizelle, President of Flanders; Master Jehan Coleghens, Burgomaster of the City of Antwerp; Master George de la Moere, High Sheriff of the Court of Law of the City of Ghent; Master Gheldolf van der Hage, High Sheriff to the Parchons, or Succession Offices of the same city; and the Sieur de Bierbecque, and Jehan Pinnock, and Jehan Dymaerzelle, and so on and so on; baillies, sheriffs, burgomasters; burgomasters, sheriffs, baillies; wooden, formal figures, stiff with velvet and damask, their heads covered by birettas of black velvet with great tassels of gold thread of Cyprusgood Flemish heads, nevertheless, dignified and sober faces, akin to those which stand out so strong and earnest from the dark background of Rembrandts Night Round; faces which all bore witness to the perspicacity of Maximilian of Austria in confiding to the full, as his manifesto ran, in their good sense, valour, experience, loyalty, and high principles. -There was one exception, however, a subtle, intelligent, crafty face, a curious mixture of the ape and the diplomatist, towards whom the Cardinal advanced three paces and bowed profoundly, but who, nevertheless, was simply named Guillaume Rym, Councillor and Pensionary2 of the City of Ghent. -Few people at that time recognised the true significance of Guillaume Rym. A rare genius who, in revolutionary times, would have appeared upon the surface of events, the fifteenth century compelled him to expend his fine capacities on underground intrigueto live in the saps, as Saint-Simon expresses it. For the rest, he found full appreciation with the first sapper of Europe, being intimately associated with Louis XI in his plots, and often had a hand in the secret machinations of the King. All of which things were entirely beyond the ken of the multitude, who were much astonished at the deferential politeness of the Cardinal towards this insignificant looking little Flemish functionary. -______________________ -1 The company and jurisdiction of the Paris lawyers, founded 1303. -2 Title, in those days, of the first Minister of State in Holland. -Chapter 4 - Master Jacques Coppenole -While the Pensionary of Ghent and his Eminence were exchanging very low bows and a few words in a tone still lower, a tall man, large-featured and of powerful build, prepared to enter abreast with Guillaume Rymthe mastiff with the foxhis felt hat and leathern jerkin contrasting oddly with all the surrounding velvet and silk. Presuming that it was some groom gone astray, the usher stopped him: -Hold, friend, this is not your way! -The man in the leathern jerkin shouldered him aside. -What does the fellow want of me? said he in a voice which drew the attention of the entire Hall to the strange colloquy; seest not that I am one of them? -Your name? demanded the usher. -Jacques Coppenole. -Your degree? -Hosier, at the sign of the Three Chains in Ghent. -The usher recoiled. To announce sheriff and burgomaster was bad enough; but a hosierno, that passed all bounds! The Cardinal was on thorns. Everybody was staring and listening. For two whole days had his Eminence been doing his utmost to lick these Flemish bears into shape in order to make them somewhat presentable in publicthis contretemps was a rude shock. -Meanwhile Guillaume Rym turned to the usher and with his diplomatic smile, Announce Ma?itre Jacques Coppenole, Clerk to the Sheriffs of the City of Ghent, he whispered to him very softly. -Usher, added the Cardinal loudly, announce Ma?tre Jacques Coppenole, Clerk to the Sheriffs of the illustrious City of Ghent. -This was a mistake. Left to himself, Guillaume Rym would have dexterously settled the difficulty; but Coppenole had heard the Cardinal. -No, Croix-Dieu! he said in a voice of thunder, Jacques Coppenole, hosier. Hearest thou, usher? Nothing more, nothing less! Gods cross! Hosier is as fine a title as any other! Many a time Monsieur the Archduke has looked for his glove1 among my hose! -There was a roar of laughter and applause. A pun is instantly taken up in Paris, and never fails of applause. -Add to this that Coppenole was one of the people, and that the throng beneath him was also composed of the people, wherefore, the understanding between them and him had been instantaneous, electric, and, so to speak, from the same point of view. The Flemish hosiers high and mighty way of putting down the courtiers stirred in these plebeian breasts a certain indefinable sense of self-respect, vague and embryonic as yet in the fifteenth century. And this hosier, who just now had held his own so stoutly before the Cardinal, was one of themselvesa most comfortable reflection to poor devils accustomed to pay respect and obedience to the servants of the servants of the Abbot of Sainte-Genevive, the Cardinals train-bearer. -Coppenole saluted his Eminence haughtily, who courteously returned the greeting of the all-powerful burgher, whom even Louis XI feared. Then, while Guillaume Rym, that shrewd and malicious man, as Philippe de Comines says, followed them both with a mocking and supercilious smile, each sought their appointed place, the Cardinal discomfited and anxious, Coppenole calm and dignified, and thinking no doubt that after all his title of hosier was as good as any other, and that Mary of Burgundy, the mother of that Margaret whose marriage Coppenole was helping to arrange, would have feared him less as cardinal than as hosier. For it was not a cardinal who would have stirred up the people of Ghent against the favourites of the daughter of Charles the Bold, and no cardinal could have hardened the crowd with a word against her tears and entreaties when the Lady of Flanders came to supplicate her people for them, even at the foot of their scaffold; whereas the hosier had but to lift his leather-clad arm, and off went your heads my fine gentlemen, Seigneur Guy dHymbercourt and Chancellor Guillaume Hugonet! -Yet this was not all that was in store for the poor Cardinal; he was to drink to the dregs the cup of humiliationthe penalty of being in such low company. -The reader may perhaps remember the impudent mendicant who, at the beginning of the Prologue, had established himself upon the projection just below the Cardinals platform. The arrival of the illustrious guests had in nowise made him quit his position, and while prelates and ambassadors were packed on the narrow platform like Dutch herrings in a barrel, the beggar sat quite at his ease with his legs crossed comfortably on the architrave. It was a unique piece of insolence, but nobody had noticed it as yet, the attention of the public being directed elsewhere. For his part, he took no notice of what was going on, but kept wagging his head from side to side with the unconcern of a Neapolitan lazzarone, and mechanically repeating his droning appeal, Charity, I pray you! Certain it was, he was the only person in the whole vast audience who never even deigned to turn his head at the altercation between Coppenole and the usher. Now, it so chanced that the master hosier of Ghent, with whom the people were already so much in sympathy and on whom all eyes were fixed, came and seated himself in the first row on the platform, just above the beggar. What was the amazement of the company to see the Flemish ambassador, after examining the strange figure beneath him, lean over and clap the ragged shoulder amicably. The beggar turnedsurprise, recognition, and pleasure beamed from the two facesthen, absolutely regardless of their surroundings, the hosier and the sham leper fell to conversing in low tones and hand clasped in hand, Clopin Trouillefous ragged arm against the cloth of gold draperies of the balustrade, looking like a caterpillar on an orange. -The novelty of this extraordinary scene excited such a stir of merriment in the Hall that the Cardinals attention was attracted. He bent forward, but being unable from where he sat to do more than catch a very imperfect glimpse of Trouillefous unsightly coat, he naturally imagined that it was merely a beggar asking alms, and, incensed at his presumption -Monsieur the Provost of the Palais, fling me this rascal into the river! he cried. -Croix-Dieu! Monseigneur the Cardinal, said Coppenole without leaving hold of Trouillefous hand, its a friend of mine. -No?l! No?l! shouted the crowd; and from that moment Master Coppenole enjoyed in Paris as in Ghent great favour with the people, as men of his stamp always do, says Philippe de Comines, when they are thus indifferent to authority. -The Cardinal bit his lip, then he leaned over to his neighbour, the Abbot of Sainte-Genevive: -Droll ambassadors these, whom Monsieur the Archduke sends to announce Madame Marguerite to us, he said in a half whisper. -Your Eminence wastes his courtesy on these Flemish hogs, returned the Abbot. Margaritas ante porcos. -Say rather, retorted the Cardinal with a smile, Porcos ante Margaritam. -This little jeu de mots sent the whole cassocked court into ecstasies. The Cardinals spirits rose somewhat; he was quits now with Coppenolehe, too, had had a pun applauded. -And now, with such of our readers as have the power to generalize an image and an idea, as it is the fashion to say nowadays, permit us to ask if they are able to form a clear picture of the scene presented by the vast parallelogram of the great Hall at the moment to which we draw their attention. In the middle of the western wall is the magnificent and spacious platform draped with cloth of gold, entered by a small Gothic doorway, through which files a procession of grave and reverend personages whose names are announced in succession by the strident voice of the usher. The first benches are already occupied by a crowd of venerable figures muffled in robes of ermine, velvet, and scarlet cloth. Around this platformon which reigns decorous silencebelow, opposite, everywhere, the seething multitude, the continuous hum of voices, all eyes fixed on every face on the platform, a thousand muttered repetitions of each name. In truth, a curious spectacle and worthy of the attention of the spectators. But stay, what is that kind of erection at the opposite end of the Hall, having four party-coloured puppets on it and four others underneath; and who is that pale figure standing beside it clad in sombre black? Alas! dear reader, it is none other than Pierre Gringoire and his Prologue, both of which we had utterly forgotten. -And that is exactly what he had feared. -From the moment when the Cardinal entered, Gringoire had never ceased to exert himself to keep his Prologue above water. First he had vehemently urged the actors, who had faltered, and stopped short, to proceed and raise their voices; then, perceiving that nobody was listening to them, he stopped them again, and during the quarter of an hour the interruption had lasted had never ceased tapping his foot impatiently, fuming, calling upon Gisquette and Linarde, urging those near him to insist on the continuation of the Prologuein vain. Not one of them would transfer his attention from the Cardinal, the Embassy, the platformthe one centre of this vast radius of vision. It must also be admitted, and we say it with regret, that by the time his Eminence appeared on the scene and caused so marked a diversion, the audience was beginning to find the Prologue just a little tedious. After all, whether you looked at the platform or the marble table, the play was the samethe conflict between Labour and Clergy, Aristocracy and Commerce. And most of them preferred to watch these personages as they lived and breathed, elbowing each other in actual flesh and blood on the platform, in the Flemish Embassy, under the Cardinals robe or Coppenoles leathern jerkin, than painted, tricked out, speaking in stilted verse, mere dummies stuffed into yellow and white tunics, as Gringoire represented them. -Nevertheless, seeing tranquility somewhat restored, our poet bethought him of a stratagem which might have been the saving of the whole thing. -Monsieur, said he, addressing a man near him, a stout, worthy person with a long-suffering countenance, now, how would it be if they were to begin it again? -What? asked the man. -Why, the Mystery, said Gringoire. -Just as you please, returned the other. -This half consent was enough for Gringoire, and taking the business into his own hands, he began calling out, making himself as much one of the crowd as possible: Begin the Mystery again! Begin again! -What the devils all the hubbub about down there? said Joannes de Molendino (for Gringoire was making noise enough for half a dozen). What, comrades, is the Mystery not finished and done with? They are going to begin again; thats not fair! -No! no! shouted the scholars in chorus. Down with the Mysterydown with it! -But Gringoire only multiplied himself and shouted the louder, Begin again! begin again! -These conflicting shouts at last attracted the attention of the Cardinal. -Monsieur the Provost of the Palais, said he to a tall man in black standing a few paces from him, have these folk gone demented that they are making such an infernal noise? -The Provost of the Palais was a sort of amphibious magistrate; the bat, as it were, of the judicial order, partaking at once of the nature of the rat and the bird, the judge and the soldier. -He approached his Eminence, and with no slight fear of his displeasure, explained in faltering accents the unseemly behaviour of the populace: how, the hour of noon having arrived before his Eminence, the players had been forced into commencing without waiting for his Eminence. -The Cardinal burst out laughing. -By my faith, Monsieur the Rector of the University might well have done likewise. What say you Ma?tre Guillaume Rym? -Monseigneur, replied Rym, let us be content with having missed half the play. That is so much gained at any rate. -Have the fellows permission to proceed with their mummeries? inquired the Provost. -Oh, proceed, proceed, returned the Cardinal; tis all one to me. Meanwhile I can be reading my breviary. -The Provost advanced to the front of the platform, and after obtaining silence by a motion of the hand, called out: -Burghers, country and townsfolk, to satisfy those who desire the play should begin again and those who desire it should finish, his Eminence orders that it should continue. -Thus both parties had to be content. Nevertheless, both author and audience long bore the Cardinal a grudge in consequence. -The persons on the stage accordingly resumed the thread of their discourse, and Gringoire hoped that at least the remainder of his great work would get a hearing. But this hope was doomed to speedy destruction like his other illusions. Silence had indeed been established to a certain extent, but Gringoire had not observed that when the Cardinal gave the order for the Mystery to proceed, the platform was far from being filled, and that the Flemish ambassadors were followed by other persons belonging to the rest of the cortg, whose names and titles, hurled intermittently by the usher into the midst of his dialogue, caused considerable havoc therein. Imagine the effect in a drama of to-day of the doorkeeper bawling between the lines, or even between the first two halves of an alexandrine, such parentheses as these: -Ma?tre Jacques Charmolue, Procurator of the King in the Ecclesiastical Court! -Jehan de Harlay, Esquire, Officer of the Mounted Night Watch of the City of Paris! -Messire Galiot de Genoilhac, Knight, Lord of Brussac, Chief of the Kings Artillery! -Ma?tre Dreux-Raguier, Inspector of Waters and Forests of our Lord the King, throughout the lands of France, Champagne, and Brie! -Messire Louis de Graville, Knight, Councillor and Chamberlain to the King, Admiral of France, Ranger of the Forest of Vincennes! -Ma?tre Denis le Mercier, Custodian to the House for the Blind in Paris! etc., etc., etc. -It was insufferable. -This peculiar accompaniment, which made it so difficult to follow the piece, was the more exasperating to Gringoire as he was well aware that the interest increased rapidly as the work advanced, and that it only wanted hearing to be a complete success. It would indeed be difficult to imagine a plot more ingeniously and dramatically constructed. The four characters of the Prologue were still engaged in bewailing their hopeless dilemma when Venus herself, vera incessu patuit dea, appeared before them, wearing a splendid robe emblazoned with the ship of the city of Paris.2 She had come to claim for herself the dolphin promised to the Most Fair. She had the support of Jupiter, whose thunder was heard rumbling in the dressing-room, and the goddess was about to bear away her prizein other words, to espouse Monsieur the Dauphin when a little girl, clad in white damask, and holding a daisy in her hand (transparent personification of Marguerite of Flanders), arrived on the scene to contest it with Venus. Coup de thatre and quick change. After a brisk dispute, Marguerite, Venus, and the side characters agreed to refer the matter to the good judgment of the Blessed Virgin. There was another fine part, that of Don Pedro, King of Mesopotamia; but it was difficult amid so many interruptions to make out exactly what was his share in the transaction. And all this had scrambled up the ladder. -But the play was done for; not one of these many beauties was heard or understood. It seemed as if, with the entrance of the Cardinal, an invisible and magic thread had suddenly drawn all eyes from the marble table to the platform, from the southern to the western side of the Hall. Nothing could break the spell, all eyes were tenaciously fixed in that direction, and each fresh arrival, his detestable name, his appearance his dress, made a new diversion. Excepting Gisquette and Linarde, who turned from time to time if Gringoire plucked them by the sleeve, and the big, patient man, not a soul was listening, not one face was turned towards the poor, deserted Morality. Gringoire looked upon an unbroken vista of profiles. -With what bitterness did he watch his fair palace of fame and poetry crumble away bit by bit! And to think that these same people had been on the point of rioting from impatience to hear his piece! And now that they had got it, they cared not a jot for itthe very same performance which had commenced amid such unanimous applause. Eternal flow and ebb of popular favour! And to think they had nearly hanged the sergeants of the Provost! What would he not have given to go back to that honey-sweet moment! -However, at last all the guests had arrived and the ushers brutal monologue perforce came to an end. Gringoire heaved a sigh of relief. The actors spouted away bravely. Then, what must Master Coppenole the hosier do but start up suddenly, and in the midst of undivided attention deliver himself of the following abominable harangue: -Messires the burghers and squires of Paris, hang me if I know what were all doing here. To be sure, I do perceive over in that corner on a sort of stage some people who look as if they were going to fight. I do not know if this is what you call a Mystery, but I am quite certain it is not very amusing. They wrestle only with their tongues. For the last quarter of an hour I have been waiting to see the first blow struck, but nothing happens. They are poltroons, and maul one another only with foul words. You should have had some fighters over from London or Rotterdam, then there would have been some pretty fisticuffing if you like blows that could have been heard out on the Place. But these are sorry folk. They should at least give us a Morris-dance or some such mummery. This is not what I had been given to expect. I had been promised a Feast of Fools and the election of a Pope. We too have our pope of fools at Ghent, in that we are behind nobody. Croix-Dieu! This is how we manage it. We get a crowd together as here; then everybody in turn thrusts his head through a hole and pulls a face at the others. The one who by universal consent makes the ugliest face is chosen Pope. Thats our way. Its most diverting. Shall we choose your Pope after the same fashion? It would at any rate be less tedious than listening to these babblers. If they like to take their turn at grimacing theyre welcome. What say you, my masters? We have here sufficiently queer samples of both sexes to give us a good Flemish laugh, and enough ugly faces to justify our hopes of a beautiful grimace. -Gringoire would fain have replied, but stupefaction, wrath, and indignation rendered him speechless. Besides, the proposal of the popular hosier was received with such enthusiasm by these townsfolk, so flattered by being addressed as squires, that further resistance was useless. There was nothing for it but to go with the stream. Gringoire buried his face in his hands, not being fortunate enough to possess a mantle wherewith to veil his countenance like the Agamemnon of Timanthes. -______________________ -1 A pun on the word gant (glove) and Gand, the French name for the city of Ghent. -2 The arms of the city of Paris show a ship on heaving billows and the motto Fluctuat nec mergitur. -Chapter 5 - Quasimodo -In a twinkling burghers, students, and Basochians had set to work, and all was ready to carry out Coppenoles suggestion. The little chapel facing the marble table was chosen as the mise en scne of the grimaces. A pane of glass was broken out of the charming rose-window above the door, leaving an empty ring of stone, through which the competitors were to thrust their heads, while two barrels, procured from goodness knows where, and balanced precariously on the top of one another, enabled them to mount up to it. It was then agreed that, in order that the impression of the grimace might reach the beholder in full unbroken purity, each candidate, whether male or female (for there could be a female pope), was to cover his face and remain concealed in the chapel till the moment of his appearance. -In an instant the chapel was filled with competitors, and the doors closed upon them. -From his place on the platform Coppenole ordered everything, directed everything, arranged everything. During the hubbub, and pretexting vespers and other affairs of importance, the Cardinal, no less disconcerted than Gringoire, retired with his whole suite, and the crowd, which had evinced so lively an interest in his arrival, was wholly unmoved by his departure. Guillaume Rym alone noticed the rout of his Eminence. -Popular attention, like the sun, pursued its even course. Starting at one end of the Hall, it remained stationary for a time in the middle, and was now at the other end. The marble table, the brocade-covered platform, had had their day; now it was the turn of the Chapel of Louis XI. The field was clear for every sort of folly; the Flemings and the rabble were masters of the situation. -The pulling of faces began. The first to appear in the openingeye-lids turned inside out, the gaping mouth of a ravening beast, the brow creased and wrinkled like the hussar boots of the Empire periodwas greeted with such a roar of inextinguishable laughter that Homer would have taken all these ragamuffins for gods. -Nevertheless, the great Hall was anything rather than Olympus, as Gringoires poor Jupiter knew to his cost. A second, a third distortion followed, to be succeeded by another and another; and with each one the laughter redoubled, and the crowd stamped and roared its delight. There was in the whole scene a peculiar frenzy, a certain indescribable sense of intoxication and fascination almost impossible to convey to the reader of our times and social habits. -Picture to yourself a series of faces representing successively every geometrical form, from the triangle to the trapezium, from the cone to the polyhedron; every human expression, from rage to lewdness; every stage of life, from the creases of the newly born to the wrinkles of hoary age; every phantasm of mythology and religion, from Faunus to Beelzebub; every animal head, from the buffalo to the eagle, from the shark to the bulldog. Conceive all the grotesques of the Pont-Neuf, those nightmares turned to stone under the hand of Germain Pilon, inspired with the breath of life, and rising up one by one to stare you in the face with gleaming eyes; all the masks of the Carnival of Venice passing in procession before youin a word, a human kaleidoscope. -The orgy became more and more Flemish. Tenniers himself could have given but a feeble idea of it; a Salvator Rosa battle-piece treated as a bacchic feast would be nearer the mark. There were no longer scholars, ambassadors, burghers, men or women; neither Clopin Trouillefou nor Gilles Lecornu nor Marie Quatrelivres nor Robin Pousse-pain. The individual was swallowed up in the universal license. The great Hall was simply one vast furnace of effrontery and unbridled mirth, in which every mouth was a yell, every countenance a grimace, every individual a posture. The whole mass shrieked and bellowed. Every new visage that came grinning and gnashing to the window was fresh fuel to the furnace. And from this seething multitude, like steam from a caldron, there rose a humshrill, piercing, sibilant, as from a vast swarm of gnats. -Oh! oh! malediction! -Oh, look at that face! -Thats no good. -Show us another. -Guillemette Maugrepuis, look at that ox-muzzle. It only wants horns. It cant be thy husband. -The next! -Ventre du pape! What sort of a face do you call that? -Hol therethats cheating! no more than the face is to be shown! -Is that Perette Callebotte?devil take herits just what she would do! -No?l! No?l! -I shall choke! -Heres one whose ears wont come through. -And so on, and so on. -To do our friend Jehan justice, however, he was still visible in the midst of the pandemonium, high up on his pillar like a ships boy in the mizzen, gesticulating like a maniac, his mouth wide open and emitting sounds that nobody heard; not because they were drowned by the all-pervading clamour, terrific as it was, but because doubtless they had reached the limit at which shrill sounds are audiblethe twelve thousand vibrations of Sauveur, or the eight thousand of Biot. -As to Gringoire, the first moment of depression over, he had regained his self-possession, had stiffened his back against adversity. -Go on, said he for the third time to his players. Go on, you speaking machines, and proceeded to pace with long strides in front of the marble table. At one moment he was seized with the desire to go and present himself at the round window, if only for the gratification of pulling a face at this thankless crowd. But no, he said to himself, that would be beneath our dignityno vengeance. We will fight on to the end. The power of poetry over the people is great. I shall yet regain my hold. We shall see which will win the day, belles-lettres or grimaces. -Alas! he was the sole spectator of his piece. -No, I am wrong. The big, patient man, whom he had already consulted at a critical moment, still faced the stage. As to Gisquette and Linarde, they had long since deserted him. -Touched to the heart by the stanchness of this audience of one, Gringoire went up to him and accosted him, shaking him gently by the arm, for the good man was leaning against the balustrade dozing comfortably. -Sir, said Gringoire, I thank you. -Sir, returned the big man with a yawn, for what? -I see the cause of your annoyance, resumed the poet. This infernal din prevents your listening in comfort. But never fear, your name shall go down to posterity. Your name, if I may ask? -Renault Chateau, Keeper of the Seal of the Chatelet of Paris, at your service. -Sir, you are the sole representative of the Muses, said Gringoire. -You are too good, sir, replied the Keeper of the Seal of Chatelet. -The one person who has paid suitable attention to the piece. What do you think of it? -Hm, hm, replied the big official drowsily. Really quite entertaining. -Gringoire had to be content with this faint praise, for the conversation was abruptly cut short by a thunder of applause mingled with shouts of acclamation. The Fools had elected their Pope. -No?l! No?l! No?l! roared the crowd from all sides. -In truth, the grimace that beamed through the broken rose-window at this moment was nothing short of miraculous. After all the facespentagonal, hexagonal, and heteroclite which had succeeded each other in the stone frame, without which had succeeded each other in the stone frame, without realizing the grotesque ideal set up by the inflamed popular imagination, nothing inferior to the supreme effort now dazzling the spectators would have sufficed to carry every vote. Master Coppenole himself applauded, and Clopin Trouillefou, who had competedand Lord knows to what heights his ugliness could attainhad to own himself defeated. We will do likewise, nor attempt to convey to the reader a conception of that tetrahedral nose, that horse-shoe mouth, of that small left eye obscured by a red and bristling brow, while the right disappeared entirely under a monstrous wart, of those uneven teeth, with breaches here and there like the crenated walls of a fortress, of that horny lip over which one of the teeth projected like an elephants tusk, of that cloven chin, nor, above all, of the expression overlying the wholean indefinable mixture of malice, bewilderment, and sadness. Picture such an cnsemble to yourself if you can. -There was not a single dissentient voice. They rushed to the Chapel and in triumph dragged forth the thrice lucky Pope of Fools. Then surprise and admiration reached the culminating pointhe had but shown his natural countenance. -Rather, let us say, his whole person was a grimace. An enormous head covered with red bristles; between the shoulders a great hump balanced by one in front; a system of thighs and legs so curiously misplaced that they only touched at the knees, and, viewed from the front, appeared like two sickles joined at the handles; huge splay feet, monstrous hands, and, with all this deformity, a nameless impression of formidable strength, agility, and couragestrange exception to the eternal rule, which decrees that strength, like beauty, shall be the outcome of harmony. -Such was he whom the Fools had chosen for their Pope. He looked like a giant broken and badly repaired. -The moment this species of Cyclops appeared in the doorway of the Chapel, standing motionless, squat, almost as broad as he was long, squared by the base, as a great man has described it, he was instantly recognised by his party-coloured coat, half red, half violet, sprinkled with little silver bells, and above all, by the perfection of his ugliness. - Tis Quasimodo the bell-ringer! shouted the people with one voice; Quasimodo the Hunchback of Notre-Dame! Quasimodo the one-eyed! Quasimodo the bandy-legged! No?l! No?l! -The poor devil had evidently a large stock of nicknames to choose from. -Let all pregnant women beware! cried the scholars. -Or those that wish to be! added Joannes. -And in effect the women hastily covered their faces. -Oh, the hideous ape! exclaimed one. -And as wicked as he is ugly, returned another. - Tis the devil himself, added a third. -I am unlucky enough to live near Notre-Dame. I hear him scrambling about the leads all night. -With the cats. -Hes forever on our roofs. -He casts spells at us down our chimneys. -The other night he came and made faces at me through my sky-light window. I thought it was a man. What a fright I got! -I am certain he goes to the witches Sabbath. He once left a broom on my leads. -Oh, his horrid hunchbacks face! -Oh, the wicked creature! -Fie upon him! -On the other hand, the men were enchanted and applauded vociferously. -Meanwhile Quasimodo, the object of all this uproar, stood grave and unmoved in the doorway of the Chapel, and suffered himself to be admired. One of the scholars, Robin Poussepain I think it was, came up and laughed in his face somewhat too close. Without a word Quasimodo seized him by the belt and tossed him into the crowd full ten paces off. -Gods cross! Holy Father! exclaimed Master Coppenole in amazement. Yours is the rarest ugliness I have ever beheld in all my born days. You deserve to be Pope of Rome, as well as of Paris. And so saying, he clapped a jovial hand on the hunchbacks shoulder. -Quasimodo did not stir. Now heres a fellow, continued Coppenole, I have a mind to dine with, even if it cost me a new douzain of twelve livres tournois. What say you? -Quasimodo made no reply. -Croix-Dieu! cried the hosier, art deaf? -As a matter of fact he was deaf. -However, he began to be annoyed by Coppenoles manner, and suddenly turned upon him with such a snarl that the Flemish giant recoiled like a bulldog before a cat. -The result of this was that a circle of terror and respect, with a radius of at least fifteen geometric paces, was formed around the alarming personage. -An old woman explained to Master Coppenole that Quasimodo was deaf. -Deaf? cried the hosier with his great Flemish guffaw; Croix-Dieu! then hes every inch a Pope! -Why, I know him! exclaimed Jehan, who by this time had clambered down from his pillar to examine the hunchback more closely. Its my brother the Archdeacons bell-ringer. Good-day, Quasimodo. -The mans a devil, growled Robin Poussepain, still giddy from his fall. He shows himself, and you discover he is a hunchback; he walks, and he is bow-legged; he looks at you, and he has only one eye; you speak to him, and he is deaf. Why, what does this Polyphemus do with his tongue? -He can speak when he likes, said the old woman. He is deaf from the bell-ringing; he is not dumb. -Thats all thats wanting to make him perfect, remarked Jehan. -And he has an eye too many. -Not at all, said Jehan judicially; a one-eyed man is more incomplete than a blind one, for he is conscious of what he lacks. -Meanwhile all the beggars, all the lackeys, all the cutpurses, had tacked themselves on to the scholars, and gone in procession to the wardrobe of the Basoche to fetch the pasteboard tiara and the mock robe reserved for the Fools Pope, with which Quasimodo permitted himself to be invested without turning a hair, and with a sort of proud docility. They then seated him on a chair, twelve officers of the Fraternity of Fools lifted him on their shoulders, and a gleam of bitter and disdainful satisfaction lit up the morose face of the Cyclops as he saw the heads of all these fine, strong, straight-limbed men beneath his misshapen feet. -Then the whole bellowing, tattered crew set itself in motion to make the customary round of the interior galleries of the Palais, before marching through the streets and by-ways of the city. -Chapter 6 - Esmeralda -We are charmed to be able to inform our readers that during this whole scene Gringoire and his piece held their own. Spurred on by him, the actors had not ceased to declaim, nor he to listen. He had contributed his share to the clamour and was determined to stand fast to the end; nor did he despair of finally regaining the attention of the public. This spark of hope revived when he beheld Quasimodo, Coppenole, and the yelling cortge of the Pope of Fools troop out of the Hall with deafening uproar, the crowd eagerly at their heels. -Good, said he, there goes the disturbing element. -But unfortunately the disturbing element comprised the entire public. In a twinkling the Hall was empty. -To be exact, a sprinkling of spectators still remained, scattered about singly or grouped round the pillarswomen, old men, and children who had had enough of the noise and the tumult. A few scholars sat astride the windows looking down into the Place. -Well, thought Gringoire, here we have at least enough to listen to the end of my Mystery. They are few, but select a lettered audience. -A moment afterward it was discovered that a band of music, which should have been immensely effective at the entry of the Blessed Virgin, was missing. Gringoire found that his musicians had been pressed into the service of the Pope of Fools. Go on without it, he said stoically. -Approaching a group of townsfolk who appeared to be discussing his play, he caught the following scraps of conversation: -Ma?tre Cheneteau, you know the H?tel de Navarre, which used to belong to M. de Nemours? -Opposite the Chapelle de Braqueyes. -Well, the fiscal authorities have just let it to Guillaume Alisandre, the historical painter, for six livres eight sols parisis a year. -How rents are rising! -Come, thought Gringoire with a sigh, at least the others are listening. -Comrades! suddenly cried one of the young rascals at the window, EsmeraldaEsmeralda down in the Place! -The name acted like a charm. Every soul in the Hall rushed to the window, clambering up the walls to see, and repeating Esmeralda! Esmeralda! while from the outside came a great burst of applause. -Now what do they mean with their Esmeralda? Gringoire inquired, clasping his hands in despair. Ah, mon Dieu! it appears that the windows are the attraction now. -He turned towards the marble table and discovered that the play had suffered an interruption. It was the moment at which Jupiter was to appear on the scene with his thunder. But Jupiter was standing stock-still below the stage. -Michel Giborne, what are you doing there? cried the exasperated poet. Is that playing your part? Get up on the stage at once. -Alas! said Jupiter, one of the scholars has just taken away the ladder. -Gringoire looked. It was but too true; the connection between the knot of his play and the untying had been cut. -Rascal, he muttered, what did he want with the ladder? -To help him to see Esmeralda, answered Jupiter, in an injured tone. He said, Hallo, heres a ladder that nobodys using, and away he went with it. -This was the last straw. Gringoire accepted it with resignation. -May the devil fly away with you! said he to the actors, and if I am paid you shall be. Whereupon he beat a retreat, hanging his head, but the last in the field, like a general who has made a good fight. -A precious set of boobies and asses, these Parisians! he growled between his teeth, as he descended the tortuous stairs of the Palais. They come to hear a Mystery, and dont listen to a word. Theyve been taken up with all the worldwith Clopin Trouillefou, with the Cardinal, with Coppenole, with Quasimodo, with the devil; but with Madame the Virgin Mary not a bit. Dolts! if I had only known! Id have given you some Virgin Marys with a vengeance. To think that I should have come here to see faces and found nothing but backs! I, a poet, to have the success of an apothecary! True, Homerus had to beg his bread through the Greek villages, and Ovidius Naso died in exile among the Muscovites. But the devil flay me if I know what they mean with their Esmeralda. To begin with, where can the word come from?ah, its Egyptian. -BOOK II -Chapter 1 - From Scylla to Charybdis -Night falls early in January. It was already dark in the streets when Gringoire quitted the Palais, which quite suited his taste, for he was impatient to reach some obscure and deserted alley where he might meditate in peace, and where the philosopher might apply the first salve to the wounds of the poet. Philosophy was his last refuge, seeing that he did not know where to turn for a nights lodging. After the signal miscarriage of his first effort, he had not the courage to return to his lodging in the Rue Grenier-sur-lEau, opposite the hay-wharf, having counted on receiving from Monsieur the Provost for his epithalamium the wherewithal to pay Ma?tre Guillaume Doulx-Sire, farmer of the cattle taxes in Paris, the six months rent he owed him; that is to say, twelve sols parisis, or twelve times the value of all he possessed in the world, including his breeches, his shirt, and his beaver. -Resting for a moment under the shelter of the little gateway of the prison belonging to the treasurer of the Sainte-Chapelle, he considered what lodging he should choose for the night, having all the pavements of Paris at his disposal. Suddenly he remembered having noticed in the preceding week, at the door of one of the parliamentary counsellors in the Rue de la Savaterie, a stone step, used for mounting on mule-back, and having remarked to himself that that stone might serve excellently well as a pillow to a beggar or a poet. He thanked Providence for having sent him this happy thought, and was just preparing to cross the Place du Palais and enter the tortuous labyrinth of the city, where those ancient sisters, the streets of la Baillerie, la Vielle-Draperie, la Savaterie, la Juiverie, etc., pursue their mazy windings, and are still standing to this day with their nine-storied houses, when he caught sight of the procession of the Pope of Fools, as it issued from the Palais and poured across his path with a great uproar, accompanied by shouts and glare of torches and Gringoires own band of music. -The sight touched his smarting vanity, and he fled. In the bitterness of his dramatic failure everything that reminded him of the unlucky festival exasperated him and made his wounds bleed afresh. -He would have crossed the Pont Saint-Michel, but children were running up and down with squibs and rockets. -A murrain on the fire-works! exclaimed Gringoire, turning back to the Pont-au-Change. In front of the houses at the entrance to the bridge they had attached three banners of cloth, representing the King, the Dauphin, and Marguerite of Flanders, and also six smaller banners or draplets on which were pourtraicts of the Duke of Austria, the Cardinal de Bourbon, M. de Beaujeu, Mme. Jeanne de France, and Monsieur the Bastard of Bourbon, and some one else, the whole lighted up by flaming cressets. The crowd was lost in admiration. -Lucky painter, Jehan Fourbault, said Gringoire with a heavy sigh, and turned his back upon the banners and the bannerets. A street opened before him so dark and deserted that it offered him every prospect of escape from all the sounds and the illuminations of the festival. He plunged into it. A few moments afterward his foot struck against an obstacle, he tripped and fell. It was the great bunch of may which the clerks of the Basoche had laid that morning at the door of one of the presidents of the parliament, in honour of the day. -Gringoire bore this fresh mishap with heroism; he picked himself up and made for the water-side. Leaving behind him the Tournelle Civile and the Tour Criminelle, and skirting the high walls of the royal gardens, ankle-deep in mud, he reached the western end of the city, and stopped for some time in contemplation of the islet of the Passeur-aux-vaches or ferry-man of the cattle, since buried under the bronze horse of the Pont-Neuf. In the gloom the islet looked to him like a black blot across the narrow, gray-white stream that separated him from it. One could just make out by a faint glimmer of light proceeding from it, the hive-shaped hut in which the ferry-man sheltered for the night. -Happy ferry-man, thought Gringoire, thou aspirest not to fame; thou composest no epithalamiums. What carest thou for royal marriages or for Duchesses of Burgundy? Thou reckest of no Marguerites but those with which April pies the meadows for thy cows to crop. And I, a poet, am hooted at, and I am shivering, and I owe twelve sous, and my shoe-soles are worn so thin they would do to glaze thy lantern. I thank thee, ferry-man; thy cabin is soothing to my sight, and makes me forget Paris. -Here he was startled out of his well-nigh lyric ecstasy by the explosion of a great double rocket which suddenly went up from the thrice happy cabin. It was the ferry-man adding his contribution to the festivities of the day by letting off some fire-works. -At this Gringoire fairly bristled with rage. -Accursed festival! cried he; is there no escape from it?not even on the cattle ferry-mans islet? -He gazed on the Seine at his feet, and a horrible temptation assailed him. -Oh, how gladly would I drown myself, said he, if only the water were not so cold! -It was then he formed the desperate resolve that, as there was no escape from the Pope of Fools, from Jehan Fourbaults painted banners, from the bunches of may, from the squibs and rockets, he would boldly cast himself into the very heart of the merry-making and go to the Place de Grve. -There at least, he reflected, I may manage to get a brand from the bonfire whereat to warm myself, and to sup off some remnant of the three great armorial devices in sugar which have been set out on the public buffets of the city. -Chapter 2 - The Place De Grve -There remains but one slight vestige of the Place de Grve as it was in those days; namely, the charming little turret at the northern angle of the square, and that, buried as it is already under the unsightly coating of whitewash which obliterates the spirited outlines of its carvings, will doubtless soon have disappeared altogether, submerged under that flood of raw, new buildings which is rapidly swallowing up all the old facades of Paris. -Those who, like ourselves, never cross the Place de Grve without a glance of pity and sympathy for the poor little turret squeezed between two squalid houses of the time of Louis XV, can easily conjure up in fancy the ensemble of edifices of which it formed a part, and so regain a complete picture of the old Gothic square of the fifteenth century. -Then, as now, it was an irregular square bounded on one side by the quay, and at the others by rows of tall, narrow, and gloomy houses. By daylight, there was much to admire in the diversity of these edifices, all sculptured in wood or stone, and offering, even then, perfect examples of the various styles of architecture in the Middle Ages, ranging from the fifteenth back to the eleventh century, from the perpendicular, which was beginning to oust the Gothic, to the Roman which the Gothic had supplanted, and which still occupied beneath it the first story of the ancient Tour de Roland, at the corner of the square adjoining the Seine on the side of the Rue de la Tannerie. At night, nothing was distinguishable of this mass of buildings but the black and jagged outline of the roofs encircling the Place with their chain of sharp-pointed gables. For herein consists one of the radical differences between the cities of that day and the present, that whereas now the fronts of the houses look on the squares and streets, then it was their backs. During the last two centuries the houses have completely turned about. -In the centre of the eastern side of the square rose a clumsy and hybrid pile formed of three separate buildings joined together. It was known by three names, which explain its history, its purpose, and its style of architecture: the Maison au Dauphin, because Charles V had inhabited it as Dauphin; the Marchandise, because it was used as the Town Hall; the Maison-aux-Piliers (domus ad pitorum), because of the row of great pillars that supported its three storeys. Here the city found all that was necessary to a good city like Paris: a chapel for its prayers, a plaidoyer or court-room wherein to hear causes and, at need, to give a sharp set-down to the Kings men-at-arms, and in the garrets an arsenal stocked with ammunition. For the good citizens of Paris knew full well that it is not sufficient at all junctures to depend either on prayer or the law for maintaining the franchises of the city, and have always some good old rusty blunderbuss or other in reserve in the attic of the H?tel de Ville. -La Grve already had that sinister aspect which it still retains owing to the execrable associations it calls up, and the frowning H?tel de Ville of Dominique Bocador which has replaced the Maison-aux-Piliers. It must be admitted that a gibbet and a pillorya justice and a ladder, as they were then calledset up side by side in the middle of the Place, went far to make the passer-by turn in aversion from this fatal spot, where so many human beings throbbing with life and health have been done to death, and which fifty years later was to engender the Saint-Vallier fever, that morbid terror of the scaffold, the most monstrous of all maladies, because it comes not from the hand of God but of man. -It is a consoling thought, let it be said in passing, to remember that the death penalty, which three centuries ago encumbered with its spiked wheels, its stone gibbets, all its dread apparatus of death permanently fixed into the ground, the Place de Grve, the Halles, the Place Dauphine, the Cours du Trahoir, the March-aux-Pourceaux or pig-market, awful Montfaucon, the Barrire-des-Sergents, the Place-au-Chats, the Porte Saint-Denis, Champeaux, the Porte Baudets, the Porte Saint-Jacques, not to mention the pillories under the jurisdiction of the Bishop, of the Chapters, of the Abbots, of the Priors; nor the judicial drownings in the Seineit is consoling, we repeat, to reflect that after losing, one by one, all the pieces of its dread panoply: its multiplicity of executions, its fantastically cruel sentences, its rack at the Grand Chatelet the leather stretcher of which had to be renewed every five yearsthat ancient suzerain of feudal society is to-day wellnigh banished from our laws and our cities, tracked from code to code, hunted from place to place, till in all great Paris it has but one dishonoured corner it can call its ownin the Place de Grve; but one wretched guillotine, furtive, craven, shameful, that always seems to fear being caught red-handed, so quickly does it vanish after dealing its fatal blow. -Chapter 3 - Besos Para Golpes1 -By the time Pierre Gringoire reached the Place de Grve he was chilled to the bone. He had made his way across the Pont-aux-Meuniersthe Millers bridgeto avoid the crowd on the Pont-au-Change and the sight of Jehan Fourbaults banners; but the wheel of the episcopal mills had splashed him as he passed, and his coat was wet through. In addition, it seemed to him that the failure of his play made him feel the cold more keenly. He hastened, therefore, to get near the splendid bonfire burning in the middle of the Place, but found it surrounded by a considerable crowd. -Perdition take these Parisians! said he to himselffor as a true dramatic poet, Gringoire was greatly addicted to monologuenow they prevent me getting near the fire and Heaven knows I have need of a warm corner! My shoes are veritable sponges, and those cursed mill-wheels have been raining upon me. Devil take the Bishop of Paris and his mills! Id like to know what a bishop wants with a mill. Does he expect he may some day have to turn miller instead of bishop? If he is only waiting for my curse to effect this transformation, he is welcome to it, and may it include his cathedral and his mills as well. Now, let us see if these varlets will make room for me. What are they doing there, Id like to know. Warming themselvesa fine pleasure indeed! Watching a pile of fagots burna grand spectacle, i faith! -On looking closer, however, he perceived that the circle was much wider than necessary for merely warming ones self at the Kings bonfire, and that such a crowd of spectators was not attracted solely by the beauty of a hundred blazing fagots. In the immense space left free between the crowd and the fire a girl was dancing, but whether she was a human being, a sprite, or an angel, was what Gringoiresceptical philosopher, ironical poet though he might bewas unable for the moment to determine, so dazzled was he by the fascinating vision. -She was not tall, but her slender and elastic figure made her appear so. Her skin was brown, but one guessed that by day it would have the warm golden tint of the Andalusian and Roman women. Her small foot too, so perfectly at ease in its narrow, graceful shoe, was quite Andalusian. She was dancing, pirouetting, whirling on an old Persian carpet spread carelessly on the ground, and each time her radiant face passed before you, you caught the flash of her great dark eyes. -The crowd stood round her open-mouthed, every eye fixed upon her, and in truth, as she danced thus to the drumming of a tambourine held high above her head by her round and delicate arms, slender, fragile, airy as a wasp, with her gold-laced bodice closely moulded to her form, her bare shoulders, her gaily striped skirt swelling out round her, affording glimpses of her exquisitely shaped limbs, the dusky masses of her hair, her gleaming eyes, she seemed a creature of some other world. -In very truth, thought Gringoire, it is a salamander a nymphtis a goddessa bacchante of Mount M?nalus! -At this moment a tress of the salamanders hair became uncoiled, and a piece of brass attached to it fell to the ground. -Why, no, said he, tis a gipsy! and all illusion vanished. -She resumed her performance. Taking up two swords from the ground, she leaned the points against her forehead, and twisted them in one direction while she herself turned in another. -True, she was simply a gipsy; but however disenchanted Gringoire might feel, the scene was not without its charm, nor a certain weird magic under the glaring red light of the bonfire, which flared over the ring of faces and the figure of the dancing girl and cast a pale glimmer among the wavering shadows at the far end of the Place, flickering over the black and corrugated front of the old Maison-aux-Piliers, or the stone arms of the gibbet opposite. -Among the many faces dyed crimson by this glow was one which, more than all the others, seemed absorbed in contemplation of the dancer. It was the face of a man, austere, calm, and sombre. His costume was hidden by the crowd pressing round him; but though he did not appear to be more than thirty-five, he was bald, showing only a few sparse locks at the temples and they already gray. The broad high forehead was furrowed, but in the deep-set eyes there glowed an extraordinary youthfulness, a fervid vitality, a consuming passion. Those eyes never moved from the gipsy, and the longer the girl danced and bounded in all the unrestrained grace of her sixteen years, delighting the populace, the gloomier did his thoughts seem to become. Ever and anon a smile and a sigh would meet upon his lips, but the smile was the more grievous of the two. -At last, out of breath with her exertion, the girl stopped, and the people applauded with all their heart. -Djali! cried the gipsy. -At this there appeared a pretty little white goat, lively, intelligent, and glossy, with gilded horns and hoofs and a gilt collar, which Gringoire had not observed before, as it had been lying on a corner of the carpet, watching its mistress dance. -Djali, said the dancing girl, it is your turn now, and seating herself, she gracefully held out her tambourine to the goat. -Now, Djali, she continued, which month of the year is it? -The goat lifted its fore-foot and tapped once on the tambourine. It was in fact the first month. The crowd applauded. -Djali, resumed the girl, reversing the tambourine, what day of the month is it? -Djali lifted her little golden hoof and gave six strokes on the tambourine. -Djali, continued the gipsy girl, again changing the position of the tambourine, what hour of the day is it? -Djali gave seven strokes. At the same instant the clock of the Maison-aux-Piliers struck seven. -The people were lost in admiration and astonishment. -There is witchcraft in this, said a sinister voice in the crowd. It came from the bald man, who had never taken his eyes off the gipsy. -The girl shuddered and turned round, but the applause burst out afresh and drowned the morose exclamation effaced it, indeed, so completely from her mind that she continued to interrogate her goat. -Djali, show us how Ma?tre Guichard Grand-Remy, captain of the town sharp-shooters, walks in the procession at Candlemas. -Djali stood up on her hind legs and began to bleat, while she strutted along with such a delightful air of gravity that the whole circle of spectators, irresistibly carried away by this parody on the devotional manner of the captain of the sharp-shooters, burst into a roar of laughter. -Djali, resumed the girl, emboldened by her increasing success, show us Ma?tre Jacques Charmolue, the Kings Procurator in the Ecclesiastical Court, when he preaches. -The goat sat up on its hind quarters and proceeded to bleat and wave its fore-feet in so comical a fashion that excepting the bad French and worse Latinit was Jacques Charmolue, gesture, accent, attitude, to the life. -The crowd applauded ecstatically. -Sacrilege! profanation! exclaimed the voice of the bald man once more. -The gipsy girl turned round again. Ah, said she, it is that hateful man! then, with a disdainful pout of her under lip, which seemed a familiar little grimace with her, she turned lightly on her heels and began collecting the contributions of the bystanders in her tambourine. -Grands blancs, petits blancs, targes, liards laigle, every description of small coin, were now showered upon her. Suddenly, just as she was passing Gringoire, he, in sheer absence of mind, thrust his hand into his pocket, so that the girl stopped in front of him. -Diable! exclaimed the poet, finding at the bottom of his pocket realityin other words, nothing. And yet, here was this pretty girl, her great eyes fixed on him, holding out her tambourine expectantly. Gringoire broke out in a cold perspiration. If he had had all Peru in his pocket, he would most certainly have handed it to the dancing girl, but Gringoire did not possess Peruand in any case America had not yet been discovered. -Fortunately an unexpected occurrence came to his relief. -Get thee gone from here, locust of Egypt! cried a harsh voice from the darkest corner of the Place. -The girl turned in alarm. This was not the voice of the bald man; it was the voice of a woman, one full of fanaticism and malice. However, the exclamation which startled the gipsy girl highly delighted a noisy band of children prowling about the Place. -Tis the recluse of the Tour-Roland! they cried with discordant shouts of laughter; tis the sachette 2 scolding again. Has she not had any supper? Lets take her something from the public buffet! and they rushed in a mass towards the Maison-aux-Piliers. -Meanwhile Gringoire had taken advantage of the dancing girls perturbation to eclipse himself, and the childrens mocking shouts reminded him that he too had had no supper. He hastened to the buffet, but the little rascals had been too quick for him, and by the time he arrived they had swept the board. There was not even a miserable piece of honey-bread at five sous the pound. Nothing was left against the wall but the slender fleur de lis and roses painted there in 1434 by Mathieu Biternein sooth, a poor kind of supper. -It is not exactly gay to have to go to bed supperless, but it is still less entertaining neither to have supped nor to know where you are going to get a bed. Yet this was Gringoires plightwithout a prospect of food or lodging. He found himself pressed on all sides by necessity, and he considered necessity extremely hard on him. He had long ago discovered this truththat Jupiter created man during a fit of misanthropy, and throughout life the destiny of the wise man holds his philosophy in a state of siege. For his own part, Gringoire had never seen the blockade so complete. He heard his stomach sound a parley, and he thought it too bad that his evil fate should be enabled to take his philosophy by famine. -He was sinking deeper and deeper into this melancholy mood, when his attention was suddenly aroused by the sound of singing, most sweet but full of strange and fantastic modulations. It was the gipsy girl. -Her voice, like her dancing and her beauty, had some indefinable and charming qualitysomething pure and sonorous; something, so to speak, soaring, winged. Her singing was a ceaseless flow of melody, of unexpected cadences, of simple phrases dotted over with shrill and staccato notes, of liquid runs that would have taxed a nightingale, but in which the harmony was never lost, of soft octave undulations that rose and fell like the bosom of the fair singer. And all the while her beautiful face expressed with singular mobility all the varying emotions of her song, from the wildest inspiration to the most virginal dignityone moment a maniac, the next a queen. -The words she sang were in a tongue unknown to Gringoire and apparently to herself, so little did the expression she put into her song fit the sense of the words. Thus, on her lips these four lines were full of sparkling gaiety: -Un cofre de gran riqueza -Halloran dentro un pilar; -Dentro del, nuevas banderas -Con figuras de espantar. 3 -And the next moment Gringoires eyes filled with tears at the expression she put into this verse: -Alarabes de cavallo -Sin poderse menear, -Con espadas, y a los cuellos -Ballestas de buen echar. 4 -However, the prevailing note in her singing was joyousness, and, like the birds, she seemed to sing from pure serenity and lightness of heart. -The gipsys song disturbed Gringoires reverie, but only as a swan ruffles the water. He listened in a sort of trance, unconscious of all around him. It was the first moment for many hours that he forgot his woes. -The respite was short. The female voice which had interrupted the gipsys dance now broke in upon her song: -Silence, grasshopper of hell! she cried out of the same dark corner of the Place. -The poor cigale stopped short. Gringoire clapped his hands to his ears. -Oh! he cried, accursed, broken-toothed saw that comes to break the lyre! -The rest of the audience agreed with him. The foul fiend take the old sachette! growled more than one of them, and the invisible spoil-sport might have had reason to repent of her attacks on the gipsy, if the attention of the crowd had not been distracted by the procession of the Pope of Fools, now pouring into the Place de Grve, after making the tour of the streets with its blaze of torches and its deafening hubbub. -This procession which our readers saw issuing from the Palais de Justice had organized itself en route, and had been recruited by all the ruffians, all the idle pickpockets and unemployed vagabonds of Paris, so that by this time it had reached most respectable proportions. -First came Egypt, the Duke of the Gipsies at the head, on horseback, with his counts on foot holding his bridle and stirrups and followed by the whole gipsy tribe, men and women, pell-mell, their children screeching on their shoulders, and all of them, duke, counts, and rabble, in rags and tinsel. Then came the Kingdom of Argot, otherwise all the vagabonds in France, marshalled in order of their various ranks, the lowest being first. Thus they marched, four abreast, bearing the divers insignia of their degrees in that strange faculty, most of them maimed in one way or another, some halt, some minus a handthe courtauds de boutanche (shoplifters), the coquillarts (pilgrims), the hubins (house-breakers), the sabouleux (sham epileptics), the calots (dotards), the francs-mitoux (schnorrers), the polissons (street rowdies), the pitres (sham cripples), the capons (card-sharpers), the malingreux (infirm), the marcandiers (hawkers), the narquois (thimble-riggers), the orphelines (pickpockets), the archisupp?ts (arch-thieves), and the cagoux (master-thieves)a list long enough to have wearied Homer himself. It was not without difficulty that in the middle of a conclave of cagoux and archisupp?ts one discovered the King of Argot, the Grand Co?sre, huddled up in a little cart drawn by two great dogs. The Kingdom of Argot was followed by the Empire of Galilee, led by Guillaume Rousseau, Emperor of Galilee, walking majestically in a purple, wine-stained robe, preceded by mummers performing sham-fights and war-dances, and surrounded by his mace-bearers, his satellites, and his clerks of the exchequer. of all came the members of the Basoche with their garlanded may-poles, their black robes, their music worthy of a witches Sabbath, and their great yellow wax candles. In the centre of this crowd the great officers of the Confraternity of Fools bore on their shoulders a sort of litter more loaded with candles than the shrine of Sainte-Genevive at the time of the plague. And on it, resplendent in cope, crosier, and mitre, sat enthroned the new Pope of the Fools, Quasimodo, the hunchback, the bell-ringer of Notre-Dame. -Each section of this grotesque procession had its special music. The gipsies scraped their balafos 5 and banged their tambourines. The Argotiersnot a very musical racehad got no further than the viol, the cow-horn, and the Gothic rebec of the twelfth century. The Empire of Galilee was not much betterscarcely that you distinguished in its music the squeak of some primitive fiddle dating from the infancy of the art, and still confined to the re-la-mi. But it was round the Fools Pope that all the musical treasures of the age were gathered in one glorious discordance treble rebecs, tenor rebecs, not to mention flutes and brasses. Alas, our readers will remember that this was Gringoires orchestra. -It would be difficult to convey an idea of the degree of beatitude and proud satisfaction which had gradually spread over the sad and hideous countenance of Quasimodo during his progress from the Palais to the Place de Grve. It was the first gleam of self-approbation he had ever experienced. Hitherto, humiliation, disdain, disgust alone had been his portion. Deaf as he was, he relished like any true Pope the acclamations of the multitude, whom he hated because he felt they hated him. What matter that his people were a rabble of Fools, of halt and maimed, of thieves, of beggars? They were a people and he was a sovereign. And he accepted seriously all this ironical applause, all this mock reverence, with which, however, we are bound to say, there was mingled a certain amount of perfectly genuine fear. For the hunchback was very strong, and though bow-legged, was active, and though deaf, was resentfulthree qualities which have a way of tempering ridicule. -For the rest, it is highly improbable that the new Pope of Fools was conscious either of the sentiments he experienced or of those which he inspired. The mind lodged in that misshapen body must inevitably be itself defective and dim, so that whatever he felt at that moment, he was aware of it but in a vague, uncertain, confused way. But joy pierced the gloom and pride predominated. Around that sombre and unhappy countenance there was a halo of light. -It was therefore not without surprise and terror that suddenly, just as Quasimodo in this semi-ecstatic state was passing the Maison-aux-Piliers in his triumphant progress, they saw a man dart from the crowd, and with a gesture of hate, snatch from his hand the crosier of gilt wood, the emblem of his mock papacy. -This bold person was the same man who, a moment before, had scared the poor gipsy girl with his words of menace and hatred. He wore the habit of an ecclesiastic, and the moment he disengaged himself from the crowd, Gringoire, who had not observed him before, recognised him. Tiens! said he with a cry of astonishment, it is my master in Hermetics, Dom Claude Frollo the Archdeacon. What the devil can he want with that one-eyed brute? He will assuredly be devoured! -Indeed, a cry of terror rose from the crowd, for the formidable hunchback had leapt from his seat, and the women turned their heads that they might not see the Archdeacon torn limb from limb. -He made one bound towards the priest, looked in his face, and fell on his knees before him. -The priest then snatched off his tiara, broke his crosier in two, and rent his cope of tinsel, Quasimodo remaining on his knees with bent head and clasped hands. -On this there began a strange dialogue between the two of signs and gestures, for neither of them uttered a word: the priest standing angry, menacing, masterful; Quasimodo prostrate before him, humbled and suppliant; and yet Quasimodo could certainly have crushed the priest with his finger and thumb. -At last, with a rough shake of the dwarfs powerful shoulder, the Archdeacon made him a sign to rise and follow him. -Quasimodo rose to his feet. -At this the Fraternity of Fools, the first stupor of surprise passed, prepared to defend their Pope thus rudely dethroned, while the Egyptians, the Argotiers, and the Basoche in a body closed yelping round the priest. -But Quasimodo, placing himself in front of the Archdeacon, brought the muscles of his brawny fists into play and faced the assailants with the snarl of an angry tiger. -The priest, returned to his gloomy gravity, signed to Quasimodo and withdrew in silence, the hunchback walking before him and scattering the crowd in his passage. -When they had made their way across the Place the curious and idle rabble made as if to follow, whereupon Quasimodo took up his position in the rear and followed the Archdeacon, facing the crowd, thick-set, snarling, hideous, shaggy, ready for a spring, gnashing his tusks, growling like a wild beast, and causing wild oscillations in the crowd by a mere gesture or a look. -So they were allowed to turn unhindered into a dark and narrow street, where no one ventured to follow them, so effectually was the entrance barred by the mere image of Quasimodo and his gnashing fangs. -A most amazing incident! said Gringoire; but where the devil am I to find a supper? -______________________ -1 A kiss brings pain. -2 Nun of the Order of the Sack, or of the Penitence of Christ. -3 -A chest richly decorated -They found in a well, -And in it new banners -With figures most terrifying. -4 -Arab horsemen they are, -Looking like statues, -With swords, and over their shoulders -Cross-bows that shoot well. -5 A primitive stringed instrument of negro origin. -Chapter 4 - The Mishaps Consequent on Following a Pretty Woman Through the Streets at Night -At a venture, Gringoire set off to follow the gipsy girl. He had seen her and her goat turn into the Rue de la Coutellerie, so he too turned down the Rue de la Coutellerie. -Why not? said he to himself. -Now, Gringoire, being a practical philosopher of the streets of Paris, had observed that nothing is more conducive to pleasant reverie than to follow a pretty woman without knowing where she is going. There is in this voluntary abdication of ones free-will, in this subordination of ones whim to that of another person who is totally unconscious of ones proceedings, a mixture of fanciful independence and blind obedience, an indefinable something between slavery and freedom which appealed to Gringoire, whose mind was essentially mixed, vacillating, and complex, touching in turn all extremes, hanging continually suspended between all human propensities, and letting one neutralize the other. He was fond of comparing himself to Mahomets coffin, attracted equally by two loadstones, and hesitating eternally between heaven and earth, between the roof and the pavement, between the fall and the ascension, between the zenith and the nadir. -Had Gringoire lived in our day, how admirably he would have preserved the golden mean between the classical and the romantic. But he was not primitive enough to live three hundred years, a fact much to be deplored; his absence creates a void only too keenly felt in these days. -For the rest, nothing disposes one more readily to follow passengers through the streetsespecially female ones, as Gringoire had a weakness for doingthan not to know where to find a bed. -He therefore walked all pensively after the girl, who quickened her pace, making her pretty little goat trot beside her, as she saw the townsfolk going home, and the taverns the only shops that had been open that daypreparing to close. -After all, he thought, she must lodge somewhere gipsy women are kind-heartedwho knows? -And he filled in the asterisks which followed this discreet break with I know not what engaging fancies. -Meanwhile, from time to time, as he passed the last groups of burghers closing their doors, he caught scraps of their conversation which broke the charmed spell of his happy imaginings. -Now it was two old men accosting each other: -Ma?tre Thibaut Fernicle, do you know that it is very cold? (Gringoire had known it ever since the winter set in.) -You are right there, Ma?tre Boniface Disome. Are we going to have another winter like three years ago, in 80, when wood cost eight sols a load? -Bah, Ma?tre Thibaut! it is nothing to the winter of 1407 when there was frost from Martinmas to Candlemas, and so sharp that at every third word the ink froze in the pen of the registrar of the parliament, which interrupted the recording of the judgments -Farther on were two gossips at their windows with candles that spluttered in the foggy air. -Has your husband told you of the accident, Mlle. La Boudraque? -No; what is it, Mlle. Turquant? -Why, the horse of M. Gilles Godin, notary at the Chatelet, was startled by the Flemings and their procession and knocked down Ma?tre Phillipot Avrillot, a Celestine lay-brother. -Is that so? -Yes, truly. -Just an ordinary horse too! Thats rather too bad. If it had been a cavalry horse, now! -And the windows were shut again; but not before Gringoire had lost the thread of his ideas. -Fortunately he soon picked it up again, and had no difficulty in resuming it, thanks to the gipsy and to Djali, who continued to walk before himtwo graceful, delicate creatures, whose small feet, pretty forms, and engaging ways he admired exceedingly, almost confounding them in his contemplation: regarding them for their intelligence and good fellowship both as girls, while for their sure-footed, light and graceful gait, they might both have been goats. -Meanwhile the streets were momentarily becoming darker and more deserted. Curfew had rung long ago, and it was only at rare intervals that one encountered a foot-passenger in the street or a light in a window. In following the gipsy, Gringoire had become involved in that inextricable maze of alleys, lanes, and culs-de-sac which surrounds the ancient burial-ground of the Holy Innocents, and which resembles nothing so much as a skein of cotton ravelled by a kitten. -Very illogical streets, i faith! said Gringoire, quite lost in the thousand windings which seemed forever to return upon themselves, but through which the girl followed a path apparently quite familiar to her, and at an increasingly rapid pace. For his part, he would have been perfectly ignorant of his whereabouts, had he not caught sight at a turning of the octagonal mass of the pillory of the Halles, the perforated top of which was outlined sharply against a solitary lighted window in the Rue Verdelet. -For some moments the girl had been aware of his presence, turning round two or three times uneasily; once, even, she had stopped short, and taking advantage of a ray of light from a half-open bakehouse door, had scanned him steadily from head to foot; then, with the little pouting grimace which Gringoire had already noticed, she had proceeded on her way. -That little moue gave Gringoire food for reflection. There certainly was somewhat of disdain and mockery in that captivating grimace. In consequence he hung his head and began to count the paving-stones, and to follow the girl at a more respectful distance. Suddenly, at a street corner which for the moment had caused him to lose sight of her, he heard her utter a piercing shriek. He hastened forward. The street was very dark, but a twist of cotton steeped in oil that burned behind an iron grating at the feet of an image of the Virgin, enabled Gringoire to descry the gipsy struggling in the arms of two men who were endeavouring to stifle her cries. The poor, frightened little goat lowered its horns and bleated piteously. -Help! help! gentlemen of the watch! cried Gringoire, advancing bravely. One of the men holding the girl turned towards himit was the formidable countenance of Quasimodo. -Gringoire did not take to his heels, but neither did he advance one step. -Quasimodo came at him, dealt him a blow that hurled him four paces off on the pavement, and disappeared rapidly into the darkness, carrying off the girl hanging limply over one of his arms like a silken scarf. His companion followed him, and the poor little goat ran after them bleating piteously. -Murder! murder! screamed the hapless gipsy. -Hold, villains, and drop that wench! thundered a voice suddenly, and a horseman sprang out from a neighbouring cross-road. -It was a captain of the Royal Archers, armed cap--pie, and sabre in hand. -He snatched the gipsy from the grasp of the stupefied Quasimodo and laid her across his saddle; and as the redoubtable hunchback, recovered from his surprise, was about to throw himself upon him and recover his prey, fifteen or sixteen archers who had followed close upon their captain appeared, broadsword in hand. It was a detachment going the night rounds by order of M. dEstouteville, commandant of the Provostry of Paris. -Quasimodo was instantly surrounded, seized, and bound. He roared, he foamed, he bit, and had it been daylight, no doubt his face alone, rendered still more hideous by rage, would have put the whole detachment to flight. But darkness deprived him of his most formidable weaponhis ugliness. -His companion had vanished during the struggle. -The gipsy girl sat up lightly on the officers saddle, put her two hands on the young mans shoulders, and regarded him fixedly for several seconds, obviously charmed by his good looks and grateful for the service he had just rendered her. -She was the first to break the silence. Infusing a still sweeter tone into her sweet voice, she said: Monsieur the Gendarme, how are you called? -Captain Ph?bus de Chateaupers, at your service, ma belle. -Thank you, she replied; and while Monsieur the Captain was occupied in twirling his mustache la Burguignonne, she slid from the saddle like a falling arrow and was goneno lightning could have vanished more rapidly. -Nombril du Pape! swore the captain while he made them tighten Quasimodos bonds. I would rather have kept the girl. -Well, captain, returned one of the men, though the bird has flown, weve got the bat safe. -Chapter 5 - Sequel of the Mishap -Gringoire, stunned by his fall, lay prone upon the pavement in front of the image of Our Lady at the corner of the street. By slow degrees his senses returned, but for some moments he lay in a kind of half-somnolent statenot without its charmsin which the airy figures of the gipsy and her goat mingled strangely with the weight of Quasimodos fist. This condition, however, was of short duration. A very lively sense of cold in that portion of his frame which was in contact with the ground woke him rudely from his dreams, and brought his mind back to the realities. -Whence comes this coolness? he hastily said to himself, and then he discovered that he was lying in the middle of the gutter. -Devil take that hunchback Cyclops! he growled as he attempted to rise. But he was still too giddy and too bruised from his fall. There was nothing for it but to lie where he was. He still had the free use of his hands, however, so he held his nose and resigned himself to his fate. -The mud of Paris, thought he drowsilyfor he now felt pretty well convinced that he would have to put up with the kennel as a bedhas a most potent stink. It must contain a large amount of volatile and nitric acids, which is also the opinion of Ma?tre Nicolas Flamel and of the alchemists. -The word alchemist suddenly recalled the Archdeacon Claude Frollo to his mind. He remembered the scene of violence of which he had just caught a glimpsethat the gipsy was struggling between two men, that Quasimodo had had a companion, and then the morose and haughty features of the Archdeacon passed vaguely through his memory. That would be strange, thought he, and immediately with this datum and from this basis began raising a fantastic edifice of hypothesis, that house of cards of the philosophers. Then, returning suddenly to the practical, Why, I am freezing! he cried. -His position was indeed becoming less and less tenable. Each molecule of water in the gutter carried away a molecule of heat from Gringoires loins, and the equilibrium between the temperature of the body and the temperature of the water was being established in a rapid and painful manner. -Presently he was assailed by an annoyance of quite another character. -A troop of children, of those little barefooted savages who in all times have run about the streets of Paris under the immemorial name of gamins, and who, when we too were young, would throw stones at us when we came out of school because our breeches were not in ragsa swarm of these young gutter-snipes came running towards the spot where Gringoire lay, laughing and shouting in a manner that showed little regard for the slumbers of their neighbours. After them they dragged some shapeless bundle, and the clatter of their wooden shoes alone was enough to wake the dead. Gringoire, who had not quite reached that pass, raised himself up on his elbow. -Oh! Hennequin Dandche! Oh! Jehan Pincebourde! they bawled at the pitch of their voices, old Eustache Moubon, the ironmonger at the corner, is just dead. Weve got his straw mattress, and were going to make a bonfire of it. Come on! -And with that they flung the mattress right on top of Gringoire, whom they had come up to without perceiving, while at the same time one of them took a handful of straw and lit it at the Blessed Virgins lamp. -Mort-Christ! gasped Gringoire, am I going to be too hot now? -The moment was critical. He was on the point of being caught between fire and water. He made a superhuman effort such as a coiner would make to escape being boiled alive staggered to his feet, heaved the mattress back upon the boys, and fled precipitately. -Holy Virgin! yelled the gamins, it is the iron-mongers ghost! -And they too ran away. -The mattress remained master of the field. Bellefort, Father Le Juge, and Corrozet assert that next day it was picked up by the clergy of that district and conveyed with great pomp and ceremony to the treasury of the Church of Saint Opportune, where, down to 1789 the sacristan drew a handsome income from the great miracle worked by the image of the Virgin at the corner of the Rue Mauconseil, the which, by its mere presence, had on the memorable night between the sixth and seventh of January, 1482, exorcised the defunct Eustache Moubon, who, to balk the devil, had, when dying, cunningly hidden his soul in his mattress. -Chapter 6 - The Broken Pitcher -After running for some time as fast as his legs could carry him without knowing whither, rushing head foremost into many a street corner, leaping gutters, traversing numberless alleys, courts, and streets, seeking flight and passage among the endless meanderings of the old street round the Halles, exploring in his blind panic what the elegant Latin of the Charters describes as tota via, cheminum et viaria, our poet suddenly drew up short, first because he was out of breath, and secondly because an unexpected idea gripped his mind. -It appears to me, Ma?tre Pierre Gringoire, he apostrophized himself, tapping his forehead, that you must be demented to run thus. Those little ragamuffins were just as frightened of you as you of them. If I mistake not, you heard the clatter of their sabots making off southward, while you were fleeing to the north. Now of two things one: either they ran away, and the mattress, forgotten in their flight, is precisely the hospitable bed you have been searching for since the morning, and which Our Lady conveys to you miraculously as a reward for having composed in her honour a Morality accompanied by triumphs and mummeries; or, on the other hand, the boys have not run away, and, in that case, they have set fire to the mattress, which will be exactly the fire you are in need of to cheer, warm, and dry you. In either casegood fire or good bedthe mattress is a gift from Heaven. The thrice-blessed Virgin Mary at the corner of the Rue Mauconseil has maybe caused Eustache Moubon to die for that identical purpose, and it is pure folly on your part to rush off headlong, like a Picard running from a Frenchman, leaving behind what you are seeking in front decidedly you are an idiot! -Accordingly, he began to retrace his steps, and with much seeking, ferreting about, nose on the scent, and ears pricked, he endeavoured to find his way back to that blessed mattress but in vain. It was one maze of intersecting houses, blind alleys, and winding streets, among which he hesitated and wavered continually, more bewildered and entangled in this network of dark alleys than he would have been in the real labyrinth of the Hotel des Tournelles. Finally he lost patience and swore aloud: A malediction upon these alleys! The devil himself must have made them after the pattern of his pitchfork! -Somewhat relieved by this outburst, next moment his nerve was completely restored by catching sight of a red glow at the end of a long, narrow street. -Heaven be praised! said he, there it isthat must be the blaze of my mattress, and likening himself to a pilot in danger of foundering in the night, Salve, he added piously, Salve maris stella! but whether this fragment of litany was addressed to the Virgin or to the mattress, we really are unable to say. -He had advanced but a few steps down the narrow street, which was on an incline, unpaved, and more and more miry as it neared the bottom, when he became aware of a curious fact. The street was not deserted. Here and there he caught sight of vague and indeterminate shapes, all crawling in the direction of the light that flickered at the end of the street, like those lumbering insects which creep at night from one blade of grass to another towards a shepherds fire. -Nothing makes one more boldly venturesome than the consciousness of an empty pocket. Gringoire, therefore, continued his way and soon came up with the last of these weird objects dragging itself clumsily after the rest. On closer inspection he perceived that it was nothing but a miserable fragment, a stump of a man hobbling along painfully on his two hands like a mutilated grasshopper with only its front legs left. As he passed this kind of human spider it addressed him in a lamentable whine: La buona mancia, signor! la buona mancia! 1 -The devil fly away with thee! said Gringoire, and me too, if I know what that means. And he passed on. -He reached another of those ambulatory bundles and examined it. It was a cripple with only one leg and one arm, but so legless and so armless that the complicated system of crutches and wooden legs on which he was supported gave him all the appearance of a scaffolding in motion. Gringoire, who dearly loved noble and classical similes, compared him in his own mind to the living tripod of Vulcan. -The living tripod greeted him as he passed by, lifting his hat to the height of Gringoires chin and holding it there like a barbers basin while he shouted in his ear: Senor caballero, para comprar un pedaso de pan! 2 -It appears, said Gringoire, that this one talks also; but its a barbarous lingo, and he is luckier than I if he understands it. Then striking his forehead with a sudden change of thoughtThat reminds mewhat the devil did they mean this morning with their Esmeralda? -He started to quicken his pace, but for the third time something barred the way. This something, or rather some one, was blind, a little blind man with a bearded, Jewish face, who, lunging in the space round him with a stick, and towed along by a great dog, snuffled out to him in a strong, Hungarian accent: Facitote caritatem! 3 -Thank goodness! exclaimed Pierre Gringoire, at last heres one who can speak a Christian language. I must indeed have a benevolent air for them to ask alms of me, considering the present exhausted condition of my purse. My friend, and he turned to the blind man, last week I sold my last shirt, or rather, as you are acquainted only with the language of Cicero, Vendidi hebdomade super transita meum ultimuman chemisam. -So saying, he turned his back on the blind man and pursued his way. But the blind man proceeded to quicken his pace at the same time, and behold the cripple and the stump also came hurrying forward with great clatter and rattle of crutches and supports, and all three tumbling over one another at poor Gringoires heels, favoured him with their several songs. Caritatem! whined the blind man. La buona mancia! piped the stump, and the cripple took up the strain with Un pedaso de pan! -Gringoire stopped his ears. Oh, tower of Babel! he cried, and set off running. The blind man ran, the cripple ran, the stump ran. -And as he penetrated farther down the street, the maimed, the halt, and the blind began to swarm round him, while one-armed or one-eyed men, and lepers covered with sores, issued from the houses, some from little streets adjacent, some from the bowels of the earth, howling, bellowing, yelping, hobbling, and clattering along, all pressing forward towards the glow and wallowing in the mud like slugs after the rain. -Gringoire, still followed by his three persecutors, and not at all sure of what would come of all this, walked on bewildered in the midst of this swarm, upsetting the halt, striding over the stumps, his feet entangled in that ant-hill of cripples, like the English captain who was beset by a legion of crabs. -It occurred to him to attempt to retrace his steps, but it was too late. The herd had closed up behind him and his three beggars held him fast. He went on, therefore, compelled at once by that irresistible flood, by fear, and by a sensation of giddiness which made the whole thing seem like some horrible nightmare. -At last he reached the end of the street. It opened into an immense square in which a multitude of scattered lights were flickering through the misty gloom. Gringoire precipitated himself into it, hoping by the speed of his legs to escape the three maimed spectres who had fastened themselves on to him. -Onde vas hombre? 4 cried the cripple, tossing aside his complicated supports and running after him with as good a pair of legs as ever measured a geometrical pace upon the pavements of Paris; while the stump, standing erect upon his feet, bonneted Gringoire with the heavy iron-rimmed platter which served him as a support, and the blind man stared him in the face with great flaming eyes. -Where am I? asked the terrified poet. -In the Court of Miracles, replied a fourth spectre who had joined them. -Truly, said Gringoire, I see that here the blind receive their sight and the lame walk, but where is the Saviour? -Their only answer was a sinister laugh. -The poor poet looked about him. He was, in fact, in that Cour des Miracles where never honest man penetrated at such an houra magic circle wherein any officer of the Chatelet or sergeant of the Provostry intrepid enough to risk entering vanished in morselsa city of thieves, a hideous sore on the face of Paris; a drain whence flowed forth each morning, to return at night, that stream of iniquity, of mendacity, and vagabondage which flows forever through the streets of a capital; a monstrous hive to which all the hornets that prey on the social order return at night, laden with their booty; a fraudulent hospital where the Bohemian, the unfrocked monk, the ruined scholar, the good-for-nothing of every nation Spaniards, Italians, Germansand of every creedJews, Turks, and infidelsbeggars covered with painted sores during the day were transformed at night into robbers: in a word, a vast green-room, serving at that period for all the actors in that eternal drama of robbery, prostitution, and murder enacted on the streets of Paris. -It was a vast open space, irregular and ill-paved, as were all the squares of Paris at that time. Fires, around which swarmed strange groups, gleamed here and there. It was one ceaseless movement and clamour, shrieks of laughter, the wailing of babies, the voices of women. The hands and heads of this crowd threw a thousand grotesque outlines on the luminous background. The light of the fires flickered over the ground mingled with huge indefinite shadows, and across it from time to time passed some animal-like man or man-like animal. The boundary lines between race and species seemed here effaced as in a pandemonium. Men, women, beasts, age, sex, health and sickness, all seemed to be in common with this people; all was shared, mingled, confounded, superimposed, each one participated in all. -The faint and unsteady gleam of the fires enabled Gringoire through all his perturbation to distinguish that the great square was enclosed in a hideous framework of ancient houses, which, with their mouldering, shrunken, stooping fronts, each pierced by one or two round lighted windows, looked to him in the dark like so many old womens heads, monstrous and cross-grained, ranged in a circle, and blinking down upon these witches revels. -It was like another and an unknown world, undreamt of, shapeless, crawling, swarming, fantastic. -Gringoire, growing momentarily more affrighted, held by the three beggars as by so many vices, bewildered by a crowd of other faces that bleated and barked round himthe luckless Gringoire strove to collect his mind sufficiently to remember whether this was really Saturdaythe witches Sabbath. But all his efforts were uselessthe link between his memory and his brain was broken; and doubtful of everything, vacillating between what he saw and what he felt, he asked himself this insoluble question: If I am I, then what is this? If this is real, then what am I? -At this moment an intelligible cry detached itself from the buzzing of the crowd surrounding him: Take him to the King! Take him to the King! -Holy Virgin! muttered Gringoire, the King of this place? He must be a goat! -To the King! To the King! they shouted in chorus. -They dragged him away, each striving to fasten his claws on him; but the three beggars would not loose their hold, and tore him from the others, yelling: He belongs to us! -The poets doublet, already sadly ailing, gave up the ghost in this struggle. -In traversing the horrible place his giddiness passed off, and after proceeding a few paces he had entirely recovered his sense of reality. He began to adapt himself to the atmosphere of the place. In the first moments there had arisen from his poets head, or perhaps quite simply and prosaically from his empty stomach, a fume, a vapour, so to speak, which, spreading itself between him and the surrounding objects, had permitted him to view them only through the incoherent mist of a nightmare, that distorting twilight of our dreams which exaggerates and misplaces every outline, crowding objects together in disproportionate groups, transforming ordinary things into chimeras and men into monstrous phantoms. By degrees, this hallucination gave place to a less bewildered, less exaggerated state of mind. The real forced itself upon himstruck upon his eyesstruck against his feet and demolished, piece by piece, the terrifying vision by which at first he had imagined himself surrounded. He now perforce was aware that he was walking not through the Styx, but through the mud; that he was being hustled not by demons, but by thieves; that not his soul, but in simple sooth his life, was in danger (since he was without that invaluable conciliator which interposes so efficaciously between the robber and the honest manthe purse); in short, on examining the orgy more closely and in colder blood, he was obliged to climb down from the witches Sabbath to the pot-house. -And, in truth, the Court of Miracles was nothing more nor less than a huge tavern; but a tavern for brigands, as red with blood as ever it was with wine. -The spectacle which presented itself to him when his ragged escort at last brought him to the goal of his march, was not calculated to incline his mind to poetry, even though it were the poetry of hell. It was more than ever the prosaic and brutal reality of the pot-house. Were we not writing of the fifteenth century, we would say that Gringoire had come down from Michael Angelo to Callot. -Round a great fire which burned on a large round flagstone, and glowed on the red-hot legs of a trivet, unoccupied for the moment, some worm-eaten tables were ranged haphazard, without the smallest regard to symmetry or order. On these tables stood a few overflowing tankards of wine or beer, and grouped round them many bacchanalian faces reddened both by the fire and wine. Here was a man, round of belly and jovial of face, noisily embracing a thick-set, brawny trollop of the streets. Here a sham soldier, whistling cheerfully while he unwound the bandages of his false wound, and unstiffened his sound and vigorous knee, strapped up since the morning in yards of ligature. Anon it was a malingreuxa malingererpreparing with celandine and oxblood his jambe de Dieu or sore leg for the morrow. Two tables farther on a coquillart with his complete pilgrims suit, cockle-shell on hat, was spelling out and practising the Plaint of Sainte-Reine in its proper sing-song tone and nasal whine. Elsewhere a young hubin was taking a lesson in epilepsy from an old sabouleux, who was teaching him how to foam at the mouth by chewing a piece of soap. Close by, a dropsical man was removing his swelling, while four or five hags at the same table were quarrelling over a child they had stolen that evening. All of which circumstances two centuries later appeared so diverting to the Court, says Sauval, that they furnished pastime to the King, and the opening scene of the royal ballet, entitled Night, which was divided into four parts and was danced on the stage of the Petit-Bourbon. And never, adds an eye-witness in 1653, were the sudden metamorphoses of the Cour des Miracles more happily represented. Benserade prepared us for it with some very pleasing verses. -Loud guffaws of laughter resounded everywhere, and obscene songs. Each one said his say, passed his criticisms, and swore freely without listening to his neighbours. Wine cups clinked and quarrels arose as the cups met, the smash of broken crockery leading further to the tearing of rags. -A great dog sat on his tail and stared into the fire. A few children mingled in this orgy. The stolen child wept and wailed; another, a bouncing boy of four, was seated with dangling legs on too high a bench, the table reaching just to his chin, and said not a word; a third was engaged in spreading over the table with his fingers the tallow from a guttering candle. ly, a very little one was squatting in the mud, and almost lost in a great iron pot, which he scraped out with a tile, drawing sounds from it which would have made Stradivarius swoon. -There was a barrel near the fire, and seated on the barrel a beggar. It was the King upon his throne. -The three who had hold of Gringoire led him up to the barrel, and the pandemonium was silent for a moment, save for the caldron tenanted by the child. -Gringoire dared not breathe or lift his eyes. -Hombre, quita tu sombrero, 5said one of the three rogues in possession of him; and before he could understand what this meant, another had snatched off his hata poor thing, it is true, but available still on a day of sunshine or of rain. -Gringoire heaved a sigh. -Meanwhile the King, from his elevated seat, demanded: What sort of a rascal is this? -Gringoire started. This voice, though speaking in menacing tones, reminded him of the one which that very morning had struck the first blow at his Mystery, as it whined in the middle of the audience, Charity, I pray! He looked up it was indeed Clopin Trouillefou. -Clopin Trouillefou, invested with the regal insignia, had not one rag the more or the less upon him. The sore on his arm had disappeared certainly, while in his hand he held one of those leather-thonged whips called boullayes, and used in those days by the sergeants of the guard to keep back the crowd. On his head he had a sort of bonnet twisted into a circle and closed at the top; but whether it was a childs cap or a kings crown it would be hard to say, so much did the two resemble one another. -However, Gringoire, without any apparent reason, felt his hopes revive a little on recognising in the King of the Court of Miracles his accursed beggar of the great Hall. -Ma?tre, he stammered, MonseigneurSireHow must I call you? he said at last, having reached the highest point of his scale, and not knowing how to mount higher nor how to descend. -Monseigneur, Your Majesty, or Comradecall me what thou wilt, only make haste. What hast thou to say in thy defence? -In my defence? thought Gringoire; I dont quite like the sound of that. I am the one, he stammered, who this morning -By the claws of the devil, broke in Clopin, thy name, rascal, and nothing more! Hark ye! thou standest before three puissant sovereignsmyself, Clopin Trouillefou, King of Tunis, successor to the Grand Co?sre, Supreme Ruler of the Kingdom of Argot; Mathis Hunyadi Spicali, Duke of Egypt and Bohemia, the yellow-vised old fellow over there with a clout round his head; Guillaume Rousseau, Emperor of Galilee, that fat fellow whos hugging a wench instead of attending to us. We are thy judges. Thou hast entered into the Kingdom of Argot without being an Argotier, and so violated the privileges of our city. Thou must pay the penalty unless thou art either a capon, a franc mitou, or a rifodthat is to say, in the argot of honest men, either a thief, a beggar, or a vagabond. Art thou any one of these? Come, justify thyselfdescribe thy qualifications. -Alas! said Gringoire, I have not that honour. I am the author -Thats enough, resumed Trouillefou without letting him finish; thou shalt go hang. A very simple matter, messieurs the honest burghers. We do unto you as we are done by. The same law that you mete out to the Truands, the Truands mete out to you again. You are to blame if that law is a bad one. No harm if now and then an honest man grin through the hempen collarthat makes the thing honourable. Come, my friend, divide thy rags cheerfully among these ladies. I am going to string thee up for the diversion of the Vagabonds, and thou shalt give them thy purse for a pour-boire. If thou hast any last mummeries to go through, thou wilt find down in that wooden mortar a very passable stone God the Father that we stole from Saint-Pierre-aux-B?ufs. Thou hast four minutes to throw thy soul at his head. -This was a formidable harangue. -Well said, by my soul! cried the Emperor of Galilee, smashing his wine pot to prop up his table. Clopin Trouillefou preaches like a Holy Pope! -Messeigneurs the Emperors and the Kings, said Gringoire coolly (for somehow or other his courage had returned to him and he spoke resolutely), you fail to understand. My name is Pierre Gringoire. I am a poet, the author of a Morality which was performed this morning in the great Hall of the Palais. -Ah! tis thou, Ma?tre, is it? answered Clopin. I was there myself, par la tte de Dieu! Well, comrade, is it any reason because thou weariedst us to death this morning that thou shouldst not be hanged to-night? -I shall not get out of this so easily, thought Gringoire. However, he had a try for it. I see no reason why the poets should not come under the head of vagabonds, he said. As to thieves, Mercurius was one -Here Clopin interrupted him: Thou wastest time with thy patter. Pardieu, man, be hanged quietly and without more ado! -Pardon me, Monsieur the King of Tunis, returned Gringoire, disputing the ground inch by inch; it is well worth your troubleone momenthear meyou will not condemn me without a hearing -In truth, his luckless voice was drowned by the hubbub around him. The child was scraping his kettle with greater vigour than ever, and, as a climax, an old woman had just placed on the hot trivet a pan of fat, which made as much noise, spitting and fizzling over the fire, as a yelling troop of children running after a mask at Carnival time. -Meanwhile, Clopin Trouillefou, after conferring a moment with his brothers of Egypt and of Galilee, the latter of whom was quite drunk, cried sharply, Silence! As neither the frying-pan nor the kettle paid any attention, but continued their duet, he jumped down from his barrel, gave one kick to the kettle, which sent it rolling ten paces from the child, and another to the frying-pan, upsetting all the fat into the fire; then he solemnly remounted his throne, heedless of the smothered cries of the child or the grumbling of the old woman, whose supper was vanishing in beautiful white flames. -At a sign from Trouillefou, the duke, the emperor, the archisupp?ts, and the cagoux came and ranged themselves round him in a horse-shoe, of which Gringoire, upon whom they still kept a tight hold, occupied the centre. It was a semicircle of rags and tatters, of pitchforks and hatchets, of reeling legs and great bare arms, of sordid, haggard, and sottish faces. In the midst of this Round Table of the riffraff, Clopin Trouillefou, as Doge of this Senate, as head of this Peerage, as Pope of this Conclave, dominated the heterogeneous mass; in the first place by the whole height of his barrel, and then by virtue of a lofty, fierce, and formidable air which made his eye flash and rectified in his savage countenance the bestial type of the vagabond race. He was like a wild boar among swine. -Look you, said he to Gringoire, stroking his unsightly chin with his horny hand. I see no reason why you should not be hanged. To be sure, the prospect does not seem to please you; but that is simply because you townsfolk are not used to ityou make such a tremendous business of it. After all, we mean you no harm. But heres one way of getting out of it for the moment. Will you be one of us? -One may imagine the effect of this suggestion on Gringoire, who saw life slipping from his grasp, and had already begun to loosen his hold on it. He clutched it again with all his might. -That will I most readily, he replied. -You consent, resumed Clopin, to enrol yourself among the members of the petite flambe (the little dagger)? -Of the Little Daggercertainly, answered Gringoire. -You acknowledge yourself a member of the Free Company? went on the King of Tunis. -Of the Free Company. -A subject of the Kingdom of Argot? -Of the Kingdom of Argot. -A Vagabond? -A Vagabond. -With heart and soul? -Heart and soul. -I would have you observe, added the King, that you will be none the less hanged for all that. -Diable! exclaimed the poet. -Only, continued Clopin imperturbably, it will take place somewhat later, with more ceremony, and at the expense of the city of Paris, on a fine stone gibbet, and by honest men. Thats some consolation. -I am glad you think so, responded Gringoire. -Then, there are other advantages. As a member of the Free Company you will have to contribute neither towards the paving, the lighting, nor the poortaxes to which the burghers of Paris are subject. -So be it, said the poet. I agree. I am a Vagabond, an Argotier, a Little Daggerwhatever you please. And, indeed, I was all that already, Monsieur the King of Tunis, for I am a philosopher and Omnia in philosophia, omnes in philosopho continenturas you are aware. -The King of Tunis knit his brows. What do you take me for, my friend? What Jew of Hungarys patter are you treating us to now? I know no Hebrew. Its not to say that because a mans a robber he must be a Jew. Nay, indeed, I do not even thieve nowI am above thatI kill. Cutthroat, yes; cutpurse, no! -Gringoire endeavoured to squeeze some extenuating plea between these brief ejaculations jerked at him by the offended monarch. I ask your pardon, monsieur, but it is not Hebrew; it is Latin. -I tell thee, retorted the enraged Clopin, that Im not a Jew, and Ill have thee hanged, ventre de synagogue! as well as that little usurer of Judea standing beside thee, and whom I hope to see some day nailed to a counter, like the bad penny that he is. -As he spoke, he pointed to the little bearded Hungarian Jew who had accosted Gringoire with Facitote caritatem, and who, understanding no other language, was much astonished that the King of Tunis should thus vent his wrath on him. -At length Monseigneur Clopins wrath abated. -So, rascal, said he to our poet, you are willing to become a Vagabond? -Willingly, replied the poet. -Willing is not all, said Clopin gruffly. Good-will never put an extra onion into the soup, and is of no value but for getting you into Paradise. Now, Paradise and Argot are two very different places. To be received into Argot you must first prove that you are good for something, and to that end you must search the manikin. -I will search, said Gringoire, anything you please. -At a sign from Clopin, several Argotiers detached themselves from the group and returned a moment afterward, bearing two posts ending in two broad wooden feet, which insured them standing firmly on the ground. To the upper end of these posts they attached a cross-beam, the whole constituting a very pretty portable gallows, which Gringoire had the satisfaction of seeing erected before him in the twinkling of an eye. It was quite complete, even to the rope swinging gracefully from the transverse beam. -What are they after now? Gringoire asked himself with some uneasiness. The jingling of little bells, which at that moment sounded on his ear, banished his anxiety, for it proceeded from a stuffed figure which the Vagabonds were hanging by the neck to the rope, a sort of scarecrow, dressed in red and covered with little tinkling bells sufficient to equip thirty Castilian mules. The jingling of these thousand bells continued for some time under the vibration of the rope, then died slowly away and sank into complete silence as the figure hung motionless. -Then Clopin, pointing to a rickety old stool placed beneath the figure, said to Gringoire, Mount that. -Death of the devil! objected Gringoire, I shall break my neck. Your stool halts like a distich of Martial: one leg is hexameter and one pentameter. -Get up, repeated Clopin. -Gringoire mounted upon the stool and succeeded, though not without some oscillations of head and arms, in finding his centre of gravity. -Now, continued the King of Tunis, twist your right foot round your left leg, and stand on tip-toe on your left foot. -Monseigneur, remonstrated Gringoire, you are determined, then, that I should break some of my limbs? -Clopin shook his head. Hark ye, friendyou talk too much. In two words, this is what you are to do: stand on tip-toe, as I told you; you will then be able to reach the manikins pocket; you will put your hand into it and pull out a purse that is there. If you do all this without a sound from one of the bells, well and good; you shall be a Vagabond. We shall then have nothing further to do but belabour you well for a week. -Ventre Dieu! I will be careful, said Gringoire. And what if I make the bells ring? -Then you will be hanged. Do you understand? -No, not at all, declared Gringoire. -Listen once more. You are to pick the manikins pocket, and if a single bell stirs during the operation you will be hanged. You understand that? -Yes, said Gringoire, I understand that. What next? -If you succeed in drawing out the purse without sounding a single bell, you are a Vagabond, and you will be soundly beaten for eight days running. You understand now, no doubt. -No, monseigneur, I do not understand. Hanged in one case, beaten in the other; where does my advantage come in? -And what about becoming a rogue? rejoined Clopin. Is that nothing? Its in your own interest that we beat you, so that you may be hardened against stripes. -I am greatly obliged to you, replied the poet. -Come, make haste! said the King with a resounding kick against his barrel. Pick the manikins pocket and be done with it. I warn you for the last time that if I hear the faintest tinkle you shall take the manikins place. -The whole crew of Argotiers applauded Clopins words, and ranged themselves in a circle round the gallows with such pitiless laughter, that Gringoire saw plainly that he was affording them too much amusement not to have cause to fear the worst. He had therefore no hope left, save perhaps in the faint chance of succeeding in the desperate task imposed upon him. He resolved to risk it, but he first addressed a fervent prayer to the man of straw whom he was preparing to rob, and whose heart he was more likely to soften than those of the rogues. These myriad bells with their little brazen tongues seemed to him like so many asps with mouths open ready to hiss and bite. -Oh, he breathed, can it be that my life depends on the faintest vibration of the smallest of these bells? Oh, he added, clasping his hands, oh, clashing, jingling, tinkling bells, be silent, I implore! -He made one more attempt with Trouillefou. -And if there should come a puff of wind? -You will be hanged, replied the other without hesitation. -Realizing that there was no respite, no delay or subterfuge possible, he bravely set about his task. He twisted his right foot round his left ankle, rose on his left foot, and stretched out his hand; but as he touched the manikin, his body, being now supported but on one foot, swayed on the stool which had but three; he clutched mechanically at the figure, lost his balance, and fell heavily to the ground, deafened by the fatal clashing of the manikins thousand bells, while the figure, yielding to the thrust of his hand, first revolved on its own axis, and then swung majestically between the two posts. -Malediction! exclaimed the poet as he fell, and he lay face downward on the earth as if dead. -Nevertheless, he heard the terrible carillon going on above his head, and the diabolical laughter of the thieves, and the voice of Trouillefou saying: Lift the fellow up and hang him double-quick! -Gringoire rose to his feet. They had already unhooked the manikin to make room for him. -The Argotiers forced him to mount the stool. Clopin then came up, passed the rope round his neck, and clapping him on the shoulders, Adieu, lami, he said. You dont escape this time, not even if you were as cunning as the Pope himself. -The word mercy died on Gringoires lips. He looked around himnot a sign of hopeall were laughing. -Bellevigne de ltoile, said the King of Tunis to a gigantic rogue, who at once stood forth from the rest, climb up on to the top beam. -Bellevigne de ltoile clambered nimbly up, and the next instant Gringoire, on raising his eyes, saw with terror that he was astride the cross-beam above his head. -Now, resumed Clopin Trouillefou, when I clap my hands, do you, Andry le Rouge, knock over the stool with your knee; Fran?ois Chante-Prune will hang on to the rascals legs, and you, Bellevigne, jump on to his shouldersbut all three at the same time, do you hear? -Gringoire shuddered. -Ready? cried Clopin Trouillefou to the three Argotiers waiting to fall on Gringoire like spiders on a fly. The poor victim had a moment of horrible suspense, during which Clopin calmly pushed into the fire with the point of his shoe some twigs of vine which the flame had not yet reached. -Ready? he repeated, and raised his hands to clap. A second more and it would have been all over. -But he stopped short, struck by a sudden idea. One moment, he said; I had forgotten. It is the custom with us not to hang a man without first asking if theres any woman who will have him. Comrade, thats your last chance. You must marry either an Argotire or the rope. -Absurd as this gipsy law may appear to the reader, he will find it set forth at full length in old English law. (See Buringtons Observations.) -Gringoire breathed again. It was the second reprieve he had had within the last half hour. Yet he could not place much confidence in it. -Hol! shouted Clopin, who had reascended his throne. Hol there! womenwenchesis there any one of you, from the witch to her cat, any jade among you wholl have this rogue? Hol Colette la Charonne! Elisabeth Trouvain! Simone Jodouyne! Marie Pidebou! Thonne-la-Longue! Brarde Fanouel! Michelle Genaille! Claude Ronge-oreille! Mathurine Girorou! Hullah! Isabeau la Thierrye! Come and look! A husband for nothing! Wholl have him? -Gringoire, in this miserable plight, was doubtless not exactly tempting. The ladies seemed but little moved at the proposal, for the unfortunate man heard them answer: No, nohang him! Then we shall all get some enjoyment out of him! -Three of them, however, did come forward and inspect him. The first, a big, square-faced young woman, carefully examined the philosophers deplorable doublet. His coat was threadbare and with more holes in it than a chestnut roaster. The woman made a wry face. An old rag, she muttered, and turning to Gringoire, Lets see thy cloak. -I have lost it, answered Gringoire. -Thy hat? -They took it from me. -Thy shoes? -The soles are coming off. -Thy purse? -Alas! stammered Gringoire, I havent a single denier parisis. -Then be hanged and welcome! retorted the woman, turning her back on him. -The second, a hideous old beldame, black and wrinkled, and so ugly as to be conspicuous even in the Court of Miracles, came and viewed him from all sides. He almost trembled lest she should take a fancy to him. But she muttered between her teeth, Hes too lean, and went away. -The third was a young girl, rosy-cheeked and not too ill-favoured. Save me! whispered the poor devil. She considered him for a moment with an air of pity, then cast down her eyes, played with a fold in her petticoat, and stood irresolute. Gringoire followed her every movement with his eyesit was the last gleam of hope. -No, she said at length, no; Guillaume Longjoue would beat me. So she rejoined the others. -Comrade, said Clopin, youve no luck. -Then, standing up on his barrel: Nobody bids? he cried, mimicking the voice of an auctioneer to the huge delight of the crowd. Nobody bids? Goinggoing and, with a sign of the head to the gallowsgone! -Bellevigne de ltoile, Andry le Rouge, Fran?ois Chante-Prune again approached Gringoire. -At that moment a cry arose among the Argotiers: La Esmeralda! la Esmeralda! -Gringoire started, and turned in the direction whence the shouts proceeded. The crowd opened and made way for a fair and radiant figure. It was the gipsy girl. -La Esmeralda? said Gringoire, amazed even in the midst of his emotions how instantaneously this magic word linked together all the recollections of his day. -This engaging creature seemed to hold sway even over the Court of Miracles by the power of her exceeding charm and beauty. The Argotiers, male and female, drew aside gently to let her pass, and their brutal faces softened at her look. -She approached the victim with her firm, light step, followed closely by her pretty Djali. Gringoire was more dead than alive. She regarded him a moment in silence. -You are going to hang this man? she asked gravely of Clopin. -Yes, sister, replied the King of Tunis; that is, unless thou wilt take him for thy husband. -She thrust out her pretty under lip. -I will take him, said she. -This confirmed Gringoire more than ever in his opinion that he had been in a dream since the morning, and that this was merely a continuation of it. The transformation, though pleasing, was violent. -They instantly unfastened the noose and let the poet descend from the stool, after which he was obliged to sit down, so overcome was he by emotion. -The Duke of Egypt proceeded without a word to bring an earthenware pitcher, which the gipsy girl handed to Gringoire, saying, Throw it on the ground. -The pitcher broke in pieces. -Brother, said the Duke of Egypt, laying hands on the two heads, she is your wife; sister, he is your husband for four years. Go your ways. -______________________ -1 Charity, kind sir! -2 Kind sir, something to buy a piece of bread! -3 Charity! -4 Whither away, man? -5 Fellow, take off thy hat. -Chapter 7 - A Wedding-Night -A Few minutes afterward our poet found himself in a warm and cosy little chamber with a vaulted roof, seated in front of a table which seemed impatient to share some of the contents of a small larder hanging on the wall close by, having a good bed in prospect, and a tte--tte with a pretty girl. The adventure smacked decidedly of witchcraft. He began to take himself seriously for the hero of a fairy-tale, and looked about him from time to time to see whether the fiery chariot drawn by winged gryphons, which alone could have transported him so rapidly from Tartarus to Paradise, were still there. At intervals, too, he steadily eyed the holes in his doublet, in order to keep a firm hold on realitynot to let the earth slip away from him altogether. His reason, tossing on delusive waves, had only this frail spar to cling to. -The girl paid apparently not the slightest heed to him, but came and went, shifting one thing and another, talking to her goat, making her little pouting grimace now and then just as if he had not been there. -At last she came and seated herself near the table, so that Gringoire could contemplate her at his leisure. -You have been young, readermaybe, indeed, you are fortunate enough to be so still. It is impossible but that more than once (and for my part I have spent whole days the best employed of my lifein this pursuit) you have followed from bush to bush, beside some running brook, on a sunny day, some lovely dragon-fly, all iridescent, blue and green, darting hither and thither, kissing the tip of every spray. Can you forget the adoring curiosity with which your thoughts and your eyes were fixed upon this little darting, humming whirlwind of purple and azure wings, in the midst of which floated an intangible form, veiled, as it were, by the very rapidity of its motion? The aerial creature, dimly discerned through all this flutter of wings, seemed to you chimerical, illusory, intangible. But when at last the dragon-fly settled on the end of a reed, and you could examine, with bated breath, the gauzy wings, the long enamel robe, the two crystal globes of eyes, what amazement seized you, and what fear lest the exquisite creature should again vanish into shadow, the vision into air. Recall these impressions, and you will readily understand Gringoires feelings as he contemplated, in her visible and palpable form, that Esmeralda, of whom, up till then, he had only caught a glimpse through a whirl of dance and song and fluttering skirts. -Sinking deeper and deeper into his reverie: So this, he said to himself, as he followed her vaguely with his eyes, this is what they meant by Esmeraldaa divine creature a dancer of the streets. So high, and yet so low. It was she who dealt the death-blow to my Mystery this morning she it is who saves my life to-night. My evil geniusmy good angel! And a pretty woman, on my soul!who must have loved me to distraction to have taken me like this. Which reminds me, said he, suddenly rising from his seat, impelled by that sense of the practical which formed the basis of his character and his philosophyIm not very clear how it came about, but the fact remains that I am her husband. -With this idea in his mind and in his eyes, he approached the girl with so enterprising and gallant an air that she drew back. -What do you want with me? said she. -Can you ask, adorable Esmeralda? responded Gringoire in such impassioned accents that he was astonished at himself. -The gipsy stared at him wide-eyed. I dont know what you mean. -What? rejoined Gringoire, growing warmer and warmer, and reflecting that after all it was only a virtue of the Court of Miracles he had to deal with, am I not thine, sweetheart; art thou not mine? and without more ado he clasped his arms about her. -The gipsy slipped through his hands like an eel; with one bound she was at the farther end of the little chamber, stooped, and rose with a little dagger in her hand before Gringoire had even time to see where she drew it from. There she stood, angry and erect, breathing fast with parted lips and fluttering nostrils, her cheeks red as peonies, her eyes darting lightning, while at the same moment the little white goat planted itself in front of her, ready to do battle with the offender, as it lowered its gilded but extremely sharp horns at him. In a twinkling the dragon-fly had turned wasp with every disposition to sting. -Our philosopher stood abashed, glancing foolishly from the goat to its mistress. -Blessed Virgin! he exclaimed as soon as his astonishment would permit him, what a pair of spitfires! -The gipsy now broke silence. -You are an impudent fellow, she said. -Pardon me, mademoiselle, retorted Gringoire with a smile, then why did you take me for your husband? -Was I to let you be hanged? -So that, returned the poet, somewhat disabused of his amorous expectations, was all you thought of in saving me from the gallows? -And what more should I have thought of, do you suppose? -Gringoire bit his lip. It seems, said he, that I am not quite so triumphant in Cupido as I imagined. But in that case, why have broken the poor pitcher? -All this time Esmeraldas dagger and the goats horns continued on the defensive. -Mademoiselle Esmeralda, said the poet, let us come to terms. As I am not the recorder at the Chatelet I shall not make difficulties about your carrying a dagger thus in Paris, in the teeth of the ordinances and prohibitions of Monsieur the Provost, though you must be aware that Noel Lescrivain was condemned only last week to pay ten sols parisis for carrying a cutlass. However, that is no affair of mine, and I will come to the point. I swear to you by my hope of salvation that I will not approach you without your consent and permission; but, I implore you, give me some supper. -Truth to tell, Gringoire, like M. Depraux, was but little inclined to sensuality. He had none of those swashbuckler and conquering ways that take girls by storm. In love, as in all other matters, he willingly resigned himself to temporizing and a middle course, and a good supper in charming tte--tte, especially when he was hungry, appeared to him an admirable interlude between the prologue and the dnouement of an amatory adventure. -The gipsy made no reply. She pouted her lips disdainfully, tossed her little head like a bird, then burst into a peal of laughter, and the dainty little weapon vanished as it had appeared, without Gringoire being able to observe where the wasp concealed its sting. -A minute afterward there appeared upon the table a loaf of bread, a slice of bacon, some wrinkled apples, and a mug of beer. Gringoire fell to ravenously. To hear the furious clatter of his fork on the earthenware platter you would have concluded that all his love had turned to hunger. -Seated opposite to him, the girl let him proceed in silence, being visibly preoccupied with some other thought, at which she smiled from time to time, while her gentle hand absently caressed the intelligent head of the goat pressed gently against her knee. A candle of yellow wax lit up this scene of voracity and musing. Presently, the first gnawings of his stomach being satisfied, Gringoire had a pang of remorse at seeing that nothing remained of the feast but one apple. You are not eating, Mademoiselle Esmeralda? -She replied with a shake of the head, and fixed her pensive gaze on the arched roof of the chamber. -Now, what in the world is she absorbed in? thought Gringoire as he followed her gaze: it cant possibly be that grinning dwarfs face carved in the keystone of the vaulting. Que diable! I can well stand the comparison! -He raised his voice: Mademoiselle! -She seemed not to hear him. -He tried again still louder: Mademoiselle Esmeralda! -Labour lost. The girls mind was elsewhere and Gringoires voice had not the power to call it back. Fortunately, the goat struck in and began pulling its mistress gently by the sleeve. -What is it, Djali? said the gipsy quickly, as if starting out of a dream. -It is hungry, said Gringoire, delighted at any opening for a conversation. -Esmeralda began crumbling some bread, which Djali ate daintily out of the hollow of her hand. -Gringoire gave her no time to resume her musings. He hazarded a delicate question. -So you will not have me for your husband? -The girl looked at him steadily. No, she said. -Nor for your lover? -She thrust out her under lip and answered No. -For a friend, then? continued Gringoire. -She regarded him fixedly, then after a moments reflection, Perhaps, she replied. -This perhaps, so dear to the philosopher, encouraged Gringoire. Do you know what friendship is? he asked. -Yes, returned the gipsy. It is to be like brother and sister; two souls that touch without mingling; two fingers of the same hand. -And love? proceeded Gringoire. -Oh, love, she said, and her voice vibrated and her eyes shone, that is to be two and yet only onea man and a woman blending into an angelit is heaven! -As she spoke, the dancing girl of the streets glowed with a beauty which affected Gringoire strangely, and which seemed to him in perfect harmony with the almost Oriental exaltation of her words. Her chaste and rosy lips were parted in a half smile, her pure and open brow was ruffled for a moment by her thoughts, as a mirror is dimmed by a passing breath, and from under her long, dark, drooping lashes there beamed a sort of ineffable light, imparting to her face that ideal suavity which later on Raphael found at the mystic point of intersection of the virginal, the human, and the divine. -Nevertheless, Gringoire continued: What must a man be, then, to win your favour? -He must be a man! -And I, said he; what am I, then? -A man goes helmet on head, sword in hand, and gilt spurs on heel. -Good, said Gringoire, the horse makes the man. Do you love any one? -As a lover? -As a lover. -She paused thoughtfully for a moment, then she said with a peculiar expression, I shall know that soon. -And why not to-night? rejoined the poet in tender accents; why not me? -She gave him a cold, grave look. I could never love a man unless he could protect me. -Gringoire reddened and accepted the rebuke. The girl evidently alluded to the feeble assistance he had rendered her in the critical situation of a couple of hours before. This recollection, effaced by the subsequent adventures of the evening, now returned to him. He smote his forehead. -That reminds me, mademoiselle, I ought to have begun by that. Pardon my foolish distraction. How did you manage to escape out of the clutches of Quasimodo? -The gipsy shuddered. Oh, the horrible hunchback! she exclaimed, hiding her face in her hands, and shivering as if overcome by violent cold. -Horrible indeed, agreed Gringoire; but how, he persisted, did you get away from him? -Esmeralda smiled, heaved a little sigh, and held her peace. -Do you know why he followed you? asked Gringoire, trying to come at the information he sought by another way. -No, I do not, answered the gipsy. But, she added sharply, you were following me too. Why did you follow me? -To tell you the honest truth, replied Gringoire, I dont know that either. -There was a pause. Gringoire was scratching the table with his knife; the girl smiled to herself and seemed to be looking at something through the wall. Suddenly she began to sing, hardly above her breath: -Quando las pintades aves -Mudas estn, y la tierra1 -She stopped abruptly, and fell to stroking Djali. -That is a pretty little animal you have there. -It is my sister, she replied. -Why do they call you Esmeralda? inquired the poet. -I dont know. -Oh, do tell me. -She drew from her bosom a little oblong bag hanging round her neck by a chain of berries. The bag, which exhaled a strong smell of camphor, was made of green silk, and had in the middle a large green glass bead like an emerald. It is perhaps because of that, said she. -Gringoire put out his hand for the little bag, but she drew back. Do not touch it! It is an amulet, and either you will do mischief to the charm, or it will hurt you. -The poets curiosity became more and more lively. Who gave it you? -She laid a finger on her lips and hid the amulet again in her bosom. He tried her with further questions, but she scarcely answered. -What does the word Esmeralda mean? -I dont know. -What language is it? -Egyptian, I think. -I thought as much, said Gringoire. You are not a native of this country? -I dont know. -Have you father or mother? -She began singing to an old air: -Mon pre est oiseau, -Ma mre est oiselle. -Je passe leau sans nacelle, -Je passe leau sans bateau. -Ma mre est oiselle, -Mon pre est oiseau.1 -Very good, said Gringoire. How old were you when you came to France? -Quite little. -And to Paris? - year. As we came through the Porte Papale I saw the reed linnet fly overhead. It was the end of August; I said, It will be a hard winter. -And so it was, said Gringoire, delighted at this turn in the conversation. I spent it in blowing on my fingers. So you have the gift of prophecy? -She lapsed again into her laconic answersNo. -That man whom you call the Duke of Egypt, is he the head of your tribe? -Yes. -Well, but it was he who united us in marriage, observed the poet timidly. -She made her favourite little grimace. Why, I dont even know your name! -My name? If you wish to know it, here it isPierre Gringoire. -I know a finer one than that, said she. -Ah, cruel one! responded the poet. Never mind, you cannot provoke me. See, perhaps you will like me when you know me better; besides, you have told me your story with so much confidence that it is only fair that I should tell you something of mine. You must know, then, that my name is Pierre Gringoire, and that my father farmed the office of notary in Gonesse. He was hanged by the Burgundians, and my mother was murdered by the Picards at the time of the siege of Paris, twenty years ago. So, at six years of age I was an orphan, with no sole to my foot but the pavement of Paris. How I got through the interval from six to sixteen I should be at a loss to tell. A fruit-seller would throw me a plum here, a baker a crust of bread there. At night I would get picked up by the watch, who put me in prison, where at least I found a truss of straw to lie upon. All this did not prevent me from growing tall and thin, as you perceive. In winter I warmed myself in the sun in the porch of the H?tel de Sens, and I thought it very absurd that the bonfires for the Feast of Saint-John should be reserved for the dog-days. At sixteen I wished to adopt a trade. I tried everything in turn. I became a soldier, but I was lacking in courage; friar, but I was not sufficiently piousbesides, I am a poor hand at drinking. In desperation I apprenticed myself to a Guild of Carpenters, but I was not strong enough. I had more inclination towards being a school-master: to be sure, I could not read, but that need not have prevented me. At last I was obliged to acknowledge that something was lacking in me for every profession; so, finding that I was good for nothing, I, of my own free will, turned poet and composer of rhythms. That is a calling a man can adopt when he is a vagabond, and is always better than robbing, as some young friends of mine, who are themselves footpads, urged me to do. One fine day I was fortunate enough to encounter Dom Claude Frollo, the reverend Archdeacon of Notre-Dame. He interested himself in me, and I owe it to him that I am to-day a finished man of letters, being well versed in Latin, from Ciceros Offices to the Mortuology of the Celestine Fathers, nor ignorant of scholastics, of poetics, of music, nor even of hermetics nor alchemythat subtlety of subtleties. Then, I am the author of the Mystery represented with great triumph and concourse of the people, filling the great Hall of the Palais de Justice. Moreover, I have written a book running to six hundred pages on the prodigious comet of 1465, over which a man lost his reason. Other successes, too, I have had. Being somewhat of an artillery carpenter, I helped in the construction of that great bombard of Jean Maugue, which, as you know, burst on the Charenton bridge the first time it was tried and killed four-and-twenty of the spectators. So, you see, I am not such a bad match. I know many very pleasing tricks which I would teach your goat; for instance, to imitate the Bishop of Paris, that accursed Pharisee whose mill-wheels splash the passengers the whole length of the Pont-aux-Meuniers. And then my Mystery play will bring me in a great deal of money, if only they pay me. In short, I am wholly at your servicemyself, my wit, my science, and my learning; ready, damoselle, to live with you as it shall please youin chastity or pleasureas man and wife, if so you think goodas brother and sister, if it please you better. -Gringoire stopped, waiting for the effect of his long speech on the girl. Her eyes were fixed on the ground. -Ph?bus, she murmured. Then, turning to the poet, Ph?bus, what does that mean? -Gringoire, though not exactly seeing the connection between his harangue and this question, was nothing loath to exhibit his erudition. Bridling with conscious pride, he answered: It is a Latin word meaning the sun. -The sun! she exclaimed. -And the name of a certain handsome archer, who was a god, added Gringoire. -A god! repeated the gipsy with something pensive and passionate in her tone. -At that moment one of her bracelets became unfastened and slipped to the ground. Gringoire bent quickly to pick it up; when he rose the girl and her goat had disappeared. He only heard the sound of a bolt being shot which came from a little door leading, doubtless, into an inner room. -Has she, at least, left me a bed? inquired our philosopher. -He made the tour of the chamber. He found no piece of furniture suitable for slumber but a long wooden chest, and its lid was profusely carved, so that when Gringoire lay down upon it he felt very much as Micromegas must have done when he stretched himself at full length to slumber on the Alps. -Well, he said, accommodating himself as best he might to the inequalities of his couch, one must make the best of it. But this is indeed a strange wedding-night. Tis a pity, too; there was something guileless and antediluvian about that marriage by broken pitcher that took my fancy. -______________________ -1 When the bright-hued birds are silent, And the earth -2 -My fathers a bird, -My mothers another. -I pass over the water -Without boat or wherry. -My mothers a bird, -And so is my father. -BOOK III -Chapter 1 - Notre-Dame -Assuredly the Cathedral of Notre-Dame at Paris is, to this day, a majestic and sublime edifice. But noble as it has remained while growing old, one cannot but regret, cannot but feel indignant at the innumerable degradations and mutilations inflicted on the venerable pile, both by the action of time and the hand of man, regardless alike of Charlemagne, who laid the first stone, and Philip Augustus, who laid the last. -On the face of this ancient queen of our cathedrals, beside each wrinkle one invariably finds a scar. Tempus edax, homo edacior, which I would be inclined to translate: Time is blind, but man is senseless. -Had we, with the reader, the leisure to examine, one by one, the traces of the destruction wrought on this ancient church, we should have to impute the smallest share to Time, the largest to men, and more especially to those whom we must perforce call artists, since, during the last two centuries, there have been individuals among them who assumed the title of architect. -And first of all, to cite only a few prominent examples, there are surely few such wonderful pages in the book of Architecture as the fa?ades of the Cathedral. Here unfold themselves to the eye, successively and at one glance, the three deep Gothic doorways; the richly traced and sculptured band of twenty-eight royal niches; the immense central rose-window, flanked by its two lateral windows, like a priest by the deacon and subdeacon; the lofty and fragile gallery of trifoliated arches supporting a heavy platform on its slender columns; finally, the two dark and massive towers with their projecting slate roofsharmonious parts of one magnificent whole, rising one above another in five gigantic storeys, massed yet unconfused, their innumerable details of statuary, sculpture, and carving boldly allied to the impassive grandeur of the whole. A vast symphony in stone, as it were; the colossal achievement of a man and a nationone and yet complexlike the Iliades and the Romances to which it is sisterprodigious result of the union of all the resources of an epoch, where on every stone is displayed in a hundred variations the fancy of the craftsman controlled by the genius of the artist; in a word, a sort of human Creation, mighty and prolific, like the divine Creation, of which it seems to have caught the double characteristicsvariety and eternity. -And what we say here of the fa?ade applies to the entire church; and what we say of the Cathedral of Paris may be said of all the ministers of Christendom in the Middle Ages. -Everything stands in its proper relation in that self-evolved art, is logical, well-proportioned. By measuring one toe you can estimate the height of the giant. -To return to the fa?ade of Notre-Dame, as we see it to-day, when we stand lost in pious admiration of the mighty and awe-inspiring Cathedral, which, according to the chroniclers, strikes the beholder with terrorqu? mole sua terrorem incutit spectantibus. -Three important things are now missing in that fa?ade: the flight of eleven steps which raised it above the level of the ground; the lower row of statues occupying the niches of the three doorways; and the upper series of twenty-eight, which filled the gallery of the first story and represented the earliest Kings of France, from Childebert to Philip Augustus, each holding in his hand the imperial orb. -The disappearance of the steps is due to Time, which by slow and irresistible degrees has raised the level of the soil of the city. But Time, though permitting these eleven steps, which added to the stately elevation of the pile, to be swallowed by the rising tide of the Paris pavement, has given to the Cathedral more perhaps than he took away; for it was the hand of Time that steeped its fa?ade in those rich and sombre tints by which the old age of monuments becomes their period of beauty. -But who has overthrown the two rows of statues? Who has left the niches empty? Who has scooped out, in the very middle of the central door, that new and bastard-pointed arch? Who has dared to hang in it, cheek by jowl with Biscornettes arabesques, that tasteless and clumsy wooden door with Louis XV carvings? Manthe architectsthe artists of our own day! -And, if we enter the interior of the edifice, who has overthrown the colossal St. Christopher, proverbial among statues as the Grande Salle of the Palais among Halls, as the spire of Strasbourg Cathedral among steeples? And the countless figureskneeling, standing, equestrian, men, women, children, kings, bishops, knights, of stone, marble, gold, silver, brass, even waxwhich peopled all the spaces between the columns of the nave and the choirwhat brutal hand has swept them away? Not that of Time. -And who replaced the ancient Gothic altar, splendidly charged with shrines and reliquaries, by that ponderous marble sarcophagus with its stone clouds and cherubs heads, which looks like an odd piece out of the Val de Grace or of the Invalides? And who was so besotted as to fix this lumbering stone anachronism into the Carlovingian pavement of Hercandus? Was it not Louis XIV, in fulfilment of the vow of Louis XIII? -And who put cold white glass in the place of those richly coloured panes which caused the dazzled eyes of our forefathers to wander undecided from the rose-window over the great doorway to the pointed ones of the chancel and back again? And what would a priest of the sixteenth century say to the fine yellow wash with which the vandal Archbishops have smeared the walls of their Cathedral? He would recollect that this was the colour the hangman painted over houses of evil-fame; he would recall the H?tel de Petit-Bourbon plastered all over with yellow because of the treason of its owner, the Conntablea yellow of so permanent a dye, says Sauval, and so well laid on, that the passage of more than a century has not succeeded in dimming its colour. He would think that the Holy Place had become infamous and would flee from it. -And if we ascend the Cathedral, passing over a thousand barbarisms of every descriptionwhat has become of the charming little belfry, fretted, slender, pointed, sonorous, which rose from the point of intersection of the transept, and every whit as delicate and as bold as its neighbour the spire (likewise destroyed) of the Sainte-Chapelle, soared into the blue, farther even than the towers. An architect of taste (1787) had it amputated, and deemed it sufficient reparation to hide the wound under the great lead plaster which looks like the lid of a sauce-pan. -Thus has the marvellous art of the Middle Ages been treated in almost every country, but especially in France. In its ruin three distinct factors can be traced, causing wounds of varying depths. First of all, Time, which has gradually made breaches here and there and gnawed its whole surface; next, religious and political revolutions, which, in the blind fury natural to them, wreaked their tempestuous passions upon it, rent its rich garment of sculpture and carving, burst in its rose-windows, broke its necklets of arabesques and figurines, tore down its statues, one time for their mitres, another time for their crowns; and finally, the various fashions, growing ever more grotesque and senseless, which, from the anarchical yet splendid deviations of the Renaissance onwards, have succeeded one another in the inevitable decadence of Architecture. Fashion has committed more crimes than revolution. It has cut to the quick, it has attacked the very bone and framework of the art; has mangled, pared, dislocated, destroyed the edificein its form as in its symbolism, in its coherence as in its beauty. This achieved, it set about renewinga thing which Time and Revolution, at least, never had the presumption to do. With unblushing effrontery, in the interests of good taste, it has plastered over the wounds of Gothic architecture with its trumpery knick-knacks, its marble ribbons and knots, its metal rosettesa perfect eruption of ovolos, scrolls, and scallops; of draperies, garlands, fringes; of marble flames and brazen clouds; of blowzy cupids and inflated cherubs, which began by devouring the face of art in the oratory of Catherine de Medicis, and ended by causing it to expire, tortured and grimacing, two centuries later, in the boudoir of Mme. Dubarry. -Thus, to sum up the points we have just discussed, the ravages that now disfigure Gothic architecture are of three distinct kinds: furrows and blotches wrought by the hand of Time; practical violencebrutalities, bruises, fracturesthe outcome of revolution, from Luther down to Mirabeau; mutilations, amputations, dislocation of members, restorations, the result of the laboursGreek, Roman, and barbarianof the professors following out the rules of Vitruvius and Vignola. That magnificent art which the Goths created has been murdered by the Academies. -To the devastations of Time and of Revolutionscarried out at least with impartiality and grandeurhave been added those of a swarm of school-trained architects, duly licensed and incorporated, degrading their art deliberately and, with all the discernment of bad taste, substituting the Louis XV fussiness for Gothic simplicity, and all to the greater glory of the Parthenon. This is the kick of the ass to the dying lion; it is the ancient oak, dead already above, gnawed at the roots by worms and vermin. -How remote is this from the time when Robert Cenalis, comparing Notre-Dame at Paris with the far-famed Temple of Diana at Ephesus, so much vaunted by the ancient pagans, which immortalized Erostratus, considered the Gallican Cathedral more excellent in length, breadth, height, and structure.1 -For the rest, Notre-Dame cannot, from the architectural point of view, be called complete, definite, classified. It is not a Roman church, neither is it a Gothic church. It is not typical of any style of architecture. Notre-Dame has not, like the Abbey of Tournus, the grave and massive squareness, the round, wide, vaulted roof, the frigid nudity, the majestic simplicity of the edifices which have their origin in the Roman arch. Nor is it like the Cathedral of Bourges, the splendid, airy, multiform, foliated, pinnacled, efflorescent product of the Gothic arch. Impossible, either, to rank it among that antique family of churchessombre, mysterious, low-pitched, cowering, as it were, under the weight of the round arch; half Egyptian, wholly hieroglyphical, wholly sacerdotal, wholly symbolical; as regards ornament, rather overloaded with lozenges and zigzags than with flowers, with flowers than animals, with animals than human figures; less the work of the architect than the Bishop, the first transformation of the art still deeply imbued with theocratic and military discipline, having its root in the Byzantine Empire, and stopping short at William the Conqueror. Nor, again, can the Cathedral be ranked with that other order of lofty, aerial churches, with their wealth of painted windows and sculptured work, with their sharp pinnacles and bold outlines; communal and citizenregarded as political symbols; free, capricious, untrammelledregarded as works of art. This is the second transformation of architectureno longer cryptic, sacerdotal, inevitable, but artistic, progressive, popularbeginning with the return from the Crusades and ending with Louis XI. -Notre-Dame is neither pure Roman, like the first, nor pure Gothic, like the second; it is an edifice of the transition period. The Saxon architect had just finished erecting the first pillars of the nave when the pointed arch, brought back by the Crusaders, arrived and planted itself victorious on the broad Roman capitals which were intended only to support round arches. Master, henceforth, of the situation, the pointed arch determined the construction of the rest of the building. Inexperienced and timid at its commencement, it remains wide and low, restraining itself, as it were, not daring to soar up into the arrows and lancets of the marvellous cathedrals of the later period. It would almost seem that it was affected by the proximity of the heavy Roman pillars. -Not that these edifices showing the transition from Roman to Gothic are less worthy of study than the pure models. They express a gradation of the art which would else be lost. It is the grafting of the pointed arch on to the circular arch. -Notre-Dame de Paris, in particular, is a curious specimen of this variety. Every surface, every stone of this venerable pile, is a page of the history not only of the country, but of science and of art. Thusto mention here only a few of the chief detailswhereas the small Porte Rouge almost touches the limits of fifteenth century Gothic delicacy, the pillars of the nave, by their massiveness and great girth, reach back to the Carlovingian Abbey of Saint-Germain-des-Prs. One would imagine that six centuries lay between that door and those pillars. Not even the Hermetics fail to find in the symbols of the grand doorway a satisfactory compendium of their science, of which the Church of Saint-Jacques-de-la-Boucherie was so complete a hieroglyph. Thus the Roman Abbeythe Church of the MysticsGothic artSaxon artthe ponderous round pillar reminiscent of Gregory VII, the alchemistic symbolism by which Nicolas Flamel paved the way for Lutherpapal unityschismSaint-Germain-des-PrsSaint-Jacques-de-la-Boucherieall are blended, combined, amalgamated in Notre-Dame. This generative Mother-Church is, among the other ancient churches of Paris, a sort of Chimera: she has the head of one, the limbs of another, the body of a thirdsomething of all. -These hybrid edifices are, we repeat, by no means the least interesting to the artist, the antiquary, and the historian. They let us realize to how great a degree architecture is a primitive matter, in that they demonstrate, as do the Cyclopean remains, the Pyramids of Egypt, the gigantic Hindu pagodas, that the greatest productions of architecture are not so much the work of individuals as of a community; are rather the offspring of a nations labour than the outcome of individual genius; the deposit of a whole people; the heaped-up treasure of centuries; the residuum left by the successive evaporations of human society; in a word, a species of formations. Each wave of time leaves its coating of alluvium, each race deposits its layer on the monuments, each individual contributes his stone to it. Thus do the beavers work, thus the bees, thus man. Babel, that great symbol of architecture, is a bee-hive. -Great edifices, like the great mountains, are the work of ages. Often art undergoes a transformation while they are waiting pending completionpendent opera interruptathey then proceed imperturbably in conformity with the new order of things. The new art takes possession of the monument at the point at which it finds it, absorbs itself into it, develops it after its own idea, and completes it if it can. The matter is accomplished without disturbance, without effort, without reaction, in obedience to an undeviating, peaceful law of naturea shoot is grafted on, the sap circulates, a fresh vegetation is in progress. Truly, there is matter for mighty volumes; often, indeed, for a universal history of mankind, in these successive layers of different periods of art, on different levels of the same edifice. The man, the artist, the individual, are lost sight of in these massive piles that have no record of authorship; they are an epitome, a totalization of human intelligence. Time is the architecta nation is the builder. -Reviewing here only Christo-European architecture, that younger sister of the great Masonic movements of the East, it presents the aspect of a huge formation divided into three sharply defined superincumbent zones: the Roman,2 the Greek, and that of the Renaissance, which we would prefer to call the Greco-Romanesque. The Roman stratum, the oldest and the lowest of the three, is occupied by the circular arch, which reappears, supported by the Greek column, in the modern and upper stratum of the Renaissance. Between the two comes the pointed arch. The edifices which belong exclusively to one or other of these three strata are perfectly distinct, uniform, and complete in themselves. The Abbey of Jumiges is one, the Cathedral of Reims another, the Sainte-Croix of Orleans is a third. But the three zones mingle and overlap one another at the edges, like the colours of the solar spectrum; hence these complex buildings, these edifices of the gradational, transitional period. One of them will be Roman as to its feet, Greek as to its body, and Greco-Romanesque as to its head. That happens when it has taken six hundred years in the building. But that variety is rare: the castle-keep of tamples is a specimen. Edifices of two styles are more frequent. Such is Notre-Dame of Paris, a Gothic structure, rooted by its earliest pillars in that Roman zone in which the portal of Saint-Denis and the nave of Saint-Germain-des-Prs are entirely sunk. Such again is the semi-Gothic Chapter Hall of Bocherville, in which the Roman layer reaches half-way up. Such is the Cathedral at Rouen, which would be wholly Gothic had not the point of its central spire reached up into the Renaissance.3 -For the rest, all these gradations, these differences, do but affect the surface of the building. Art has changed its skin, but the actual conformation of the Christian Church has remained untouched. It has ever the same internal structure, the same logical disposition of the parts. Be the sculptured and decorated envelope of a cathedral as it will, underneath, at least, as germ or rudiment, we invariably find the Roman basilica. It develops itself unswervingly on this foundation and following the same rules. There are invariably two naves crossing each other at right angles, the upper end of which, rounded off in a half circle, forms the choir; there are always two lower-pitched side-aisles for the processionsthe chapelssort of lateral passages communicating with the nave by its intercolumnar spaces. These conditions once fulfilled, the number of chapels, doorways, steeples, spires, may be varied to infinity, according to the fancy of the age, the nation, or the art. The proper observances of worship once provided for and insured, architecture is free to do as she pleases. Statues, stained glass, rose-windows, arabesques, flutings, capitals, bas-reliefsall these flowers of fancy she distributes as best suits her particular scheme of the moment. Hence the prodigious variety in the exterior of these edifices, in the underlying structure of which there rules so much order and uniformity. The trunk of the tree is unchanging; its vegetation only is variable. -______________________ -1 Histoire Gallicane, Book ii, period ii, fol. 130, p. 4.Authors note. -2 This is also known, according to situation, race, or style, as Lombard, Saxon, or Byzantine: four sister and parallel architectures, each having its own peculiar characteristics, but all deriving from the same principlethe circular arch. Facies non omnibus una, non diversa tamen, qualem, etc.Authors Note. -3 This part of the spire, which was of timber, was destroyed by lightning in 1823.Authors Note. -Chapter 2 - A Virds-eye View of Paris -We have endeavoured to restore for the reader this admirable Cathedral of Notre-Dame. We have briefly enumerated most of the beauties it possessed in the fifteenth century, though lost to it now; but we have omitted the chief onethe view of Paris as it then appeared from the summits of the towers. -When, after long gropings up the dark perpendicular stair-case which pierces the thick walls of the steeple towers, one emerged at last unexpectedly on to one of the two high platforms inundated with light and air, it was in truth a marvellous picture spread out before you on every side; a spectacle sui generis of which those of our readers can best form an idea who have had the good fortune to see a purely Gothic city, complete and homogeneous, of which there are still a few remaining, such as Nuremberg in Bavaria, Vittoria in Spain, or even smaller specimens, provided they are well-preserved, like Vitr in Brittany and Nordhausen in Prussia. -The Paris of that day, the Paris of the fifteenth century, was already a giant city. We Parisians in general are mistaken as to the amount of ground we imagine we have gained since then. Paris, since the time of Louis XI, has not increased by much more than a third; and, truth to tell, has lost far more in beauty than ever it has gained in size. -Paris first saw the light on that ancient island in the Seine, the Cit, which has, in fact, the form of a cradle. The strand of this island was its first enclosure, the Seine its first moat. -For several centuries Paris remained an island, with two bridges, one north, the other south, and two bridge heads, which were at once its gates and its fortresses: the Grand-Chatelet on the right bank, the Petit-Chatelet on the left. Then, after the kings of the first generation, Paris, finding itself too cramped on its island home, where it no longer had room to turn round, crossed the river; whereupon, beyond each of the bridge-fortresses, a first circle of walls and towers began to enclose pieces of the land on either side of the Seine. Of this ancient wall some vestiges were still standing in the last century; to-day, nothing is left but the memory, and here and there a tradition, such as the Baudets or Baudoyer Gateporta bagauda. -By degrees the flood of dwellings, constantly pressing forward from the heart of the city, overflows, saps, eats away, and finally swallows up this enclosure. Philip Augustus makes a fresh line of circumvallation, and immures Paris within a chain of massive and lofty towers. For upward of a century the houses press upon one another, accumulate, and rise in this basin like water in a reservoir. They begin to burrow deeper in the ground, they pile storey upon storey, they climb one upon another, they shoot up in height like all compressed growth, and each strives to raise its head above its neighbour for a breath of air. The streets grow ever deeper and narrower, every open space fills up and disappears, till, finally, the houses overleap the wall of Philip Augustus, and spread themselves joyfully over the country like escaped prisoners, without plan or system, gathering themselves together in knots, cutting slices out of the surrounding fields for gardens, taking plenty of elbow-room. -By 1367, the town has made such inroads on the suburb that a new enclosure has become necessary, especially on the right bank, and is accordingly built by Charles V. But a town like Paris is in a state of perpetual growthit is only such cities that become capitals. They are the reservoirs into which are directed all the streamsgeographical, political, moral, intellectualof a country, all the natural tendencies of the people; wells of civilization, so to speakbut also outletswhere commerce, manufacture, intelligence, population, all that there is of vital fluid, of life, of soul, in a people, filters through and collects incessantly, drop by drop, century by century. The wall of Charles V, however, endures the same fate as that of Philip Augustus. By the beginning of the fifteenth century it, too, is overstepped, left behind, the new suburb hurries on, and in the sixteenth century it seems visibly to recede farther and farther into the depths of the old city, so dense has the new town become outside it. -Thus, by the fifteenth centuryto go no fartherParis had already consumed the three concentric circles of wall, which, in the time of Julian the Apostate, were in embryo, so to speak, in the Grand-Chatelet and the Petit-Chatelet. The mighty city had successively burst its four girdles of wall like a child grown out of last years garments. Under Louis XI, clusters of ruined towers belonging to the old fortified walls were still visible, rising out of the sea of houses like hilltops out of an inundationthe archipelagoes of the old Paris, submerged beneath the new. -Since then, unfortunately for us, Paris has changed again; but it has broken through one more enclosure, that of Louis XV, a wretched wall of mud and rubbish, well worthy of the King who built it and of the poet who sang of it: -Le mur murant Paris rend Paris murmurant.1 -In the fifteenth century Paris was still divided into three towns, perfectly distinct and separate, having each its peculiar features, speciality, manners, customs, privileges, and history: the City, the University, the Town. The City, which occupied the island, was the oldest and the smallest of the triothe mother of the other twolooking, if we may be allowed the comparison, like a little old woman between two tall and blooming daughters. The University covered the left bank of the Seine from the Tournelle to the Tour de Neslepoints corresponding in the Paris of to-day to the Halles-aux-Vins and the Mint, its circular wall taking in a pretty large portion of that ground on which Julian had built his baths.2 It also included the Hill of Sainte-Genevive. The outermost point of the curving wall was the Papal Gate; that is to say, just about the site of the Panthon. The Town, the largest of the three divisions of Paris, occupied the right bank. Its quay, interrupted at several points, stretched along the Seine from the Tour de Billy to the Tour du Bois; that is, from the spot where the Grenier dAbondance now stands to that occupied by the Tuileries. These four points at which the Seine cut through the circumference of the capitalla Tournelle and the Tour de Nesle on the left, the Tour de Billy and the Tour de Bois on the right bankwere called par excellence the four towers of Paris. The Town encroached more deeply into the surrounding country than did the University. The farthest point of its enclosing wall (the one built by Charles V) was at the gates of Saint-Denis and Saint-Martin, the situation of which has not changed. -As we have already stated, each of these three great divisions of Paris was a townbut a town too specialized to be complete, a town which could not dispense with the other two. So, too, each had its peculiarly characteristic aspect. In the City, churches were the prevailing feature; in the Town, palaces; in the University, colleges. Setting aside the less important originalities of Paris and the capricious legal intricacies of the right of way, and taking note only of the collective and important masses in the chaos of communal jurisdictions, we may say that, broadly speaking, the island belonged to the Bishop, the right bank to the Provost of the Merchants Guild, and the left bank to the Rector of the University. The Provost of Parisa royal, not a municipal officehad authority over all. The City boasted Notre-Dame; the Town, the Louvre and the H?tel-de-Ville; the University, the Sorbonne. Again, the Town had the Halles, the City the H?tel-Dieu, the University the Pr-aux-Clercs.3 Crimes committed by the students on the right bank, were tried on the island in the Palais de Justice, and punished on the right bank at Montfaucon, unless the Rector, feeling the University to be strong and the King weak, thought fit to intervene; for the scholars enjoyed the privilege of being hanged on their own premises. -Most of these privileges (we may remark in passing), and there were some of even greater value than this, had been extorted from the kings by mutiny and revolts. It is the immemorial course: Le roi ne lache que quand le peuple arrachethe King only gives up what the people wrest from him. There is an old French charter which defines this popular loyalty with great simplicity: Civibus fidelitas in reges, qu? tamen aliquoties seditionibus interrupta, multa peperit privilegia.4 -In the fifteenth century the Seine embraced five islands within the purlieus of Paris: the Louvre, on which trees then grew; the ?le-aux-Vaches and the ?le Notre-Dame, both uninhabited except for one poor hovel, both fiefs of the Bishop (in the seventeenth century these two islands were made into one and built upon, now known as the ?le Saint-Louis); finally the City, having at its western extremity the islet of the Passeur-aux-Vachesthe cattle ferrynow buried under the foundations of the Pont Neuf. The City had, in those days, five bridgesthree on the right: the Pont Notre-Dame and the Pont-au-Change being of stone, and the Pont-aux-Meuniers of wood; and two on the left: the Petit-Pont of stone, and the Pont Saint-Michel of woodall lined with houses. The University had six gates built by Philip Augustus, namelystarting from the Tournellethe Porte Saint-Victor, the Porte Bordelle, the Porte Papale, the Porte Saint-Jacques, the Porte Saint-Michel and the Porte Saint-Germain. The Town also had six gates, built by Charles V, namelystarting from the Tour de Billythe Porte Saint-Antoine, the Porte du Temple, the Porte Saint-Martin, the Porte Saint-Denis, the Porte Montmartre and the Porte Saint-Honor. All these gates were strong, and at the same time handsomewhich is no detriment to strength. A wide and deep fosse, filled during the winter months with a swift stream supplied by the Seine, washed the foot of the walls all round Paris. At night the gates were shut, the river was barred at the two extremities of the town by the massive iron chains, and Paris slept in peace. -From a birds-eye view, these three great divisionsthe City, the University, and the Townpresented each an inextricably tangled network of streets to the eye. Nevertheless, one recognised at a glance that the three fragments formed together a single body. You at once distinguished two long, parallel streets running, without a break or deviation, almost in a straight line through all these towns from end to end, from south to north, at right angles with the Seine; connecting, mingling, transfusing them, incessantly pouring the inhabitants of one into the walls of the other, blending the three into one. One of these two streets ran from the Porte Saint-Jacques to the Porte Saint-Martin, and was called Rue Saint-Jacques in the University, Rue de la Juiverie (Jewry) in the City, and Rue Saint-Martin in the Town, crossing the river twice, as the Petit-Pont and the Pont Notre-Dame. The secondwhich was called Rue de la Harpe on the left bank, Rue de la Barillerie on the island, Rue Saint-Denis on the right bank, Pont Saint-Michel on one arm of the Seine, Pont-au-Change on the otherran from the Porte Saint-Michel in the University to the Porte Saint-Denis in the Town. For the rest, under however many names, they were still only the two streets, the two thoroughfares, the two mother-streets, the main arteries of Paris, from which all the other ducts of the triple city started, or into which they flowed. -Independently of these two principal streets, cutting diametrically through the breadth of Paris and common to the entire capital, the Town and the University had each its own main street running in the direction of their length, parallel to the Seine, and intersecting the two arterial streets at right angles. Thus, in the Town you descended in a straight line from the Porte Saint-Antoine to the Porte Saint-Honor; in the University, from the Porte Saint-Victor to the Porte Saint-Germain. These two great thoroughfares, crossing the two first mentioned, formed the frame on to which was woven the knotted, tortuous network of the streets of Paris. In the inextricable tangle of this network, however, on closer inspection, two sheaf-like clusters of streets could be distinguished, one in the University, one in the Town, spreading out from the bridges to the gates. Something of the same geometrical plan still exists. -Now, what aspect did this present when viewed from the top of the towers of Notre-Dame in 1482? -That is what we will endeavour to describe. -To the spectator, arrived breathless on this summit, the first glance revealed only a bewildering jumble of roofs, chimneys, streets, bridges, squares, spires, and steeples. Everything burst upon the eye at oncethe carved gable, the high, pointed roof, the turret clinging to the corner wall, the stone pyramid of the eleventh century, the slate obelisk of the fifteenth, the round, stark tower of the donjon-keep, the square and elaborately decorated tower of the church, the large, the small, the massive, the airy. The gaze was lost for long and completely in this maze, where there was nothing that had not its own originality, its reason, its touch of genius, its beauty; where everything breathed of art, from the humblest house with its painted and carved front, its visible timber framework, its low-browed doorway and projecting storeys, to the kingly Louvre itself, which, in those days, boasted a colonnade of towers. But here are the most important points which struck the eye when it became somewhat accustomed to this throng of edifices. -To begin with, the City. The island of the City, as Sauval observeswho, with all his pompous verbosity, sometimes hits upon these happy turns of phrasethe island of the City is shaped like a great ship sunk into the mud and run aground lengthwise, about mid-stream of the Seine. As we have already shown, in the fifteenth century this ship was moored to the two banks of the Seine by five bridges. This likeness to a ship had also struck the fancy of the heraldic scribes; for, according to Favyn and Pasquier, it was from this circumstance, and not from the siege by the Normans, that is derived the ship emblazoned in the arms of Paris. To him who can decipher it, heraldry is an algebra, a complete language. The whole history of the later half of the Middle Ages is written in heraldry, as is that of the first half in the symbolism of the Roman churchesthe hieroglyphics of feudalism succeeding those of theocracy. -The City, then, first presented itself to the view, with its stern to the east and its prow to the west. Facing towards the prow there stretched an endless line of old roofs, above which rose, broad and domed, the lead-roofed transept of the Sainte-Chapelle, like an elephant with its tower, except that here the tower was the boldest, airiest, most elaborate and serrated spire that ever showed the sky through its fretted cone. Just in front of Notre-Dame three streets opened into the Cathedral closea fine square of old houses. On the south side of this glowered the furrowed, beetling front of the H?tel-Dieu, with its roof as if covered with boils and warts. Then, on every side, right, left, east, and west, all within the narrow circuit of the City, rose the steeples of its twenty-one churches, of all dates, shapes, and sizes, from the low, wormeaten Roman belfry of Saint-Denis du Pas (carcer Glaucini) to the slender, tapering spires of Saint-Pierre aux B?ufs and Saint-Landry. Behind Notre-Dame northward, stretched the cloister with its Gothic galleries; southward, the semi-Roman palace of the Bishop, and eastward, an uncultivated piece of ground, the terrain, at the point of the island. Furthermore, in this sea of houses, the eye could distinguish, by the high, perforated mitres of stone which at that period capped even its topmost attic windows, the palace presented by the town, in the reign of Charles VI, to Juvnal des Ursins; a little farther on, the black-barred roofs of the market-shed in the March Palus; farther off still, the new chancel of Saint-Germain le Vieux, lengthened in 1458 by taking in a piece of the Rue aux Febves, with here and there a glimpse of causeway, crowded with people, some pillory at a corner of the street, some fine piece of the pavement of Philip Augustusmagnificent flagging, furrowed in the middle for the benefit of the horses, and so badly replaced in the middle of the sixteenth century by the wretched cobblestones called pav de la Ligue; some solitary court-yard with one of those diaphanous wrought-iron stair-case turrets they were so fond of in the fifteenth century, one of which is still to be seen in the Rue des Bourdonnais. ly, to the right of the Sainte-Chapelle, westward, the Palais de Justice displayed its group of towers by the waters edge. The trees of the royal gardens, which occupied the western point of the island, hid the ferry-mans islet from view. As for the water, it was hardly visible on either side of the City from the towers of Notre-Dame: the Seine disappeared under the bridges, and the bridges under the houses. -And when one looked beyond these bridges, on which the house-roofs glimmered greenmoss-grown before their time from the mists of the riverand turned ones gaze to the left towards the University, the first building which caught the eye was a low, extensive cluster of towers, the Petit-Chatelet, whose yawning gateway swallowed up the end of the Petit-Pont. Then, if you ran your eye along the river bank from east to west, from the Tournelle to the Tour de Nesle, it was one long line of houses with sculptured beams, coloured windows, overhanging storeys jutting out over the roadwayan interminable zigzag of gabled houses broken frequently by the opening of some street, now and then by the frontage or corner of some grand mansion with its gardens and its court-yards, its wings and outbuildings; standing proudly there in the midst of this crowding, hustling throng of houses, like a grand seigneur among a mob of rustics. There were five or six of these palaces along the quay, from the Logis de Lorraine, which shared with the Bernardines the great neighbouring enclosure of the Tournelle, to the Tour de Nesle, the chief tower of which formed the boundary of Paris, and whose pointed gables were accustomed, for three months of the year, to cut with their black triangles the scarlet disk of the setting sun. -Altogether, this side of the Seine was the least mercantile of the two: there was more noise and crowding of scholars than artisans, and there was no quay, properly speaking, except between the Pont Saint-Michel and the Tour de Nesle. The rest of the river bank was either a bare strand, like that beyond the Bernardine Monastery, or a row of houses with their feet in the water, as between the two bridges. This was the domain of the washerwomen; here they called to one another, chattered, laughed, and sang, from morning till night along the river side, while they beat the linen vigorouslyas they do to this day, contributing not a little to the gaiety of Paris. -The University itself appeared as one block forming from end to end a compact and homogeneous whole. Seen from above, this multitude of closely packed, angular, clinging roofs, built, for the most part, on one geometrical principle, gave the impression of the crystallization of one substance. Here the capricious cleavage of the streets did not cut up the mass into such disproportionate slices. The forty-two colleges were distributed pretty equally over the whole, and were in evidence on all sides. The varied and charming roof-lines of these beautiful buildings originated in the same art which produced the simple roofs they overtopped, being practically nothing more than a repetition, in the square or cube, of the same geometrical figure. Consequently, they lent variety to the whole without confusing it, completed without overloading itfor geometry is another form of harmony. Several palatial residences lifted their heads sumptuously here and there above the picturesque roofs of the left bank: the Logis de Nevers, the Logis de Rome, the Logis de Reims, which have disappeared; also the H?tel de Cluny, which for the consolation of the artist still exists, but the tower of which was so stupidly shortened a few years ago. Near the H?tel Cluny stood the Baths of Julian, a fine Roman palace with circular arches. There was, besides, a number of abbeys, more religious in style, of graver aspect than the secular residences, but not inferior either in beauty or in extent. The most striking of these were the Bernardines Abbey with its three steeples; Sainte-Genevi ve, the square tower of which still exists to make us more deeply regret the rest; the Sorbonne, part college, part monastery, of which so admirable a nave still survives; the beautiful quadrilateral Monastery of the Mathurins;5 adjacent to it the Benedictine Monastery, within the wall of which they managed to knock up a theatre between the issue of the seventh and eighth editions of this book; the Abbey of the Cordeliers, with its three enormous gables in a row; that of the Augustines, the tapering spire of which was, after the Tour de Nesle, the second pinnacle at this side of Paris, counting from the west. The colleges, the connecting link between the cloister and the world, held architecturally the mean between the great mansions and the abbeys, more severe in their elegance, more massive in their sculpture than the palaces, less serious in their style of architecture than the religious houses. Unfortunately, scarcely anything remains of these buildings, in which Gothic art held so admirable a balance between the sumptuous and the simple. The churches (and they were numerous and splendid in the University quarter, illustrating every architectural era, from the Roman arches of Saint-Julien to the Gothic arches of Saint-Sverin)the churches dominated the whole, and as one harmony more in that sea of harmonies they pierced in quick succession the waving, fretted outline of the gabled roofs with their boldly cut spires, their steeples, their tapering pinnacles, themselves but a magnificent exaggeration of the sharp angles of the roofs. -The ground of the University quarter was hilly, swelling in the southeast to the vast mound of the Montagne Sainte-Genevive. It was curious to note, from the heights of Notre-Dame, the multitude of narrow and tortuous streets (now the Quartier Latin), the clusters of houses, spreading helter-skelter in every direction down the steep sides of this hill to the water-edge, some apparently rushing down, others climbing up, and all clinging one to the other. -The inhabitants thronging the streets looked, from that height and at that distance, like a swarm of ants perpetually passing and repassing each other, and added greatly to the animation of the scene. -And here and there, in the spaces between the roofs, the steeples, the innumerable projections which so fantastically bent and twisted and notched the outermost line of the quarter, you caught a glimpse of a moss-grown wall, a thick-set round tower, an embattled, fortress-like gatewaythe wall of Philip Augustus. Beyond this stretched the verdant meadows, ran the great high-roads with a few houses straggling along their sides, growing fewer the farther they were removed from the protecting barrier. Some of these suburbs were considerable. There was firsttaking the Tournelle as the point of departurethe market-town of Saint-Victor, with its one-arched bridge spanning the Bivre; its Abbey, where the epitaph of King Louis the Fatepitaphium Ludovici Grossiwas to be seen; and its church with an octagonal spire, flanked by four belfry towers of the eleventh century (there is a similar one still to be seen at tampes). Then there was Saint-Marceau, which already boasted three churches and a convent; then, leaving on the left the mill of the Gobelins with its white wall of enclosure, you came to the Faubourg Saint-Jacques with its beautifully carved stone cross at the cross-roads; the Church of Saint-Jacques du Haut-Pas, then a charming Gothic structure; Saint-Magloire, with a beautiful nave of the fourteenth century, which Napoleon turned into a hayloft; and Notre-Dame-des-Champs, which contained some Byzantine mosaics. Finally, after leaving in the open fields the Chartreux Monastery, a sumptuous edifice contemporary to the Palais de Justice with its garden divided off into compartments, and the deserted ruins of Vauvert, the eye turned westward and fell upon the three Roman spires of Saint-Germain-des-Prs, in the rear of which the market-town of Saint-Germain, already quite a large parish, formed fifteen or twenty streets, the sharp steeple of Saint-Sulpice marking one of the corners of the town boundary. Close by was the square enclosure of the Foire Saint-Germain, where the fairs were heldthe present market-place. Then came the abbots pillory, a charming little round tower, capped by a cone of lead; farther on were the tile-fields and the Rue du Four, leading to the manorial bakehouse; then the mill on its raised mound; finally, the Lazarette, a small, isolated building scarcely discernible in the distance. -But what especially attracted the eye and held it long was the Abbey itself. Undoubtedly this monastery, in high repute both as a religious house and as a manor, this abbey-palace, wherein the Bishop of Paris esteemed it a privilege to pass one night; with a refectory which the architect had endowed with the aspect, the beauty, and the splendid rose-window of a cathedral; its elegant Lady Chapel; its monumental dormitories, its spacious gardens, its portcullis, its drawbridge, its belt of crenated wall, which seemed to stamp its crested outline on the meadow beyond, its court-yards where the glint of armour mingled with the shimmer of gold-embroidered vestmentsthe whole grouped and marshalled round the three high Roman towers firmly planted on a Gothic transeptall this, I say, produced a magnificent effect against the horizon. -When at length, after long contemplating the University, you turned towards the right bankthe Townthe scene changed its character abruptly. Much larger than the University quarter, the Town was much less of a united whole. The first glance showed it to be divided into several singularly distinct areas. First, on the east, in that part of the Town which still takes its name from the maraisthe morass into which Camulognes led C?sarthere was a great group of places extending to the waters edge. Four huge mansions, almost contiguousthe H?tels Jouy, Sens, Barbeau, and the Logis de la Reine mirrored in the Seine their slated roofs and slender turrets. These four edifices filled the space between the Rue des Nonaindires to the Celestine Abbey, the spire of which formed a graceful relief to their line of gables and battlements. Some squalid, moss-grown hovels overhanging the water in front of these splendid buildings were not sufficient to conceal from view the beautifully ornamented corners of their fa?ades, their great square stone casements, their Gothic porticoes surmounted by statues, the bold, clear-cut parapets of their walls, and all those charming architectural surprises which give Gothic art the appearance of forming her combinations afresh for each new structure. Behind these palaces ran in every direction, now cleft, palisaded, and embattled like a citadel, now veiled by great trees like a Carthusian monastery, the vast and multiform encircling wall of that marvellous H?tel Saint-Pol, where the King of France had room to lodge superbly twenty-two princes of the rank of the Dauphin and the Duke of Burgundy with their retinues and their servants, not to mention the great barons, and the Emperor when he came to visit Paris, and the lions, who had a palace for themselves within the royal palace. And we must observe here that a princes lodging comprised in those days not less than eleven apartments, from the state chamber to the oratory, besides all the galleries, the baths, the sweating-rooms, and other superfluous places with which each suite of apartments was providednot to mention the gardens specially allotted to each guest of the King, nor the kitchens, store-rooms, pantries, and general refectories of the household; the inner court-yards in which were situated twenty-two general offices, from the bake-house to the royal cellarage; the grounds for every sort and description of gamemall, tennis, tilting at the ring, etc.; aviaries, fish-ponds, menageries, stables, cattle-sheds, libraries, armouries, and foundries. Such was, at that day, a Kings palacea Louvre, an H?tel Saint-Pola city within a city. -From the tower on which we have taken up our stand, one obtained of the H?tel Saint-Pol, though half-hidden by the four great mansions we spoke of, a very considerable and wonderful view. You could clearly distinguish in it, though skilfully welded to the main building by windowed and pillared galleries, the three mansions which Charles V had absorbed into his palace: the H?tel du Petit-Muce with the fretted parapet that gracefully bordered its roof; the H?tel of the Abbot of Saint-Maur, having all the appearance of a fortress, with its massive tower, its machicolations, loopholes, iron bulwarks, and over the great Saxon gate, between the two grooves for the drawbridge, the escutcheon of the Abbot; the H?tel of the Comte dtampes, of which the keep, ruined at its summit, was arched and notched like a cocks-comb; here and there, three or four ancient oaks grouped together in one great bushy clump; a glimpse of swans floating on clear pools, all flecked with light and shadow; picturesque corners of innumerable court-yards; the Lion house, with its low Gothic arches on short Roman pillars, its iron bars and continuous roaring; cutting right through this picture the scaly spire of the Ave-Maria Chapel; on the left, the left, the Mansion of the Provost of Paris, flanked by four delicately perforated turrets; and, in the centre of it all, the H?tel Saint-Pol itself, with its multiplicity of facades, its successive enrichments since the time of Charles V, the heterogeneous excrescences with which the fancy of the architects had loaded it during two centuries, with all the roofs of its chapels, all its gables, its galleries, a thousand weather-cocks turning to the four winds of heaven, and its two lofty, contiguous towers with conical roofs surrounded by battlements at the base, looking like peaked hats with the brim turned up. -Continuing to mount the steps of this amphitheatre of palaces, rising tier upon tier in the distance, having crossed the deep fissure in the roofs of the Town which marked the course of the Rue Saint-Antoine, the eye travelled on to the Logis dAngoulme, a vast structure of several periods, parts of which were glaringly new and white, blending with the rest about as well as a crimson patch on a blue doublet. Nevertheless, the peculiarly sharp and high-pitched roof of the modern palacebristling with sculptured gargoyles, and covered with sheets of lead, over which ran sparkling incrustations of gilded copper in a thousand fantastic arabesques this curiously damascened roof rose gracefully out of the brown ruins of the ancient edifice, whose massive old towers, bulging cask-like with age, sinking into themselves with decrepitude, and rent from top to bottom, looked like great unbuttoned waistcoats. Behind rose the forest of spires of the Palais des Tournelles. No show-place in the worldnot even Chambord or the Alhambracould afford a more magical, more ethereal, more enchanting spectacle than this grove of spires, bell-towers, chimneys, weather-cocks, spiral stair-cases; of airy lantern towers that seemed to have been worked with a chisel; of pavilions; of spindle-shaped turrets, all diverse in shape, height, and position. It might have been a gigantic chess-board in stone. -That sheaf of enormous black towers to the right of the inky Tournelles, pressing one against the other, and bound together, as it were, by a circular moat; that donjon-keep, pierced far more numerously with shot-holes than with windows, its drawbridge always raised, its portcullis always loweredthat is the Bastile. Those objects like black beaks projecting from the embrasures of the battlements, and which, from a distance, you might take for rain-spouts, are cannon. Within their range, at the foot of the formidable pile, is the Porte Saint-Antoine, crouching between its two towers. -Beyond the Tournelles, reaching to the wall of Charles V, stretched in rich diversity of lawns and flower-beds a velvet carpet of gardens and royal parks, in the heart of which, conspicuous by its maze of trees and winding paths, one recognised the famous labyrinthine garden presented by Louis XI to Coictier. The great physicians observatory rose out of the maze like a massive, isolated column with a tiny house for its capital. Many a terrible astrological crime was perpetrated in that laboratory. This is now the Place Royale. -As we have said, the Palace quarter, of which we have endeavoured to convey some idea to the reader, though merely pointing out the chief features, filled the angle formed by the Seine and the wall of Charles V on the east. The centre of the Town was occupied by a congeries of dwelling-houses. For it was here that the three bridges of the City on the right bank discharged their streams of passengers; and bridges lead to the building of houses before palaces. This collection of middle-class dwellings, closely packed together like the cells of a honeycomb, was, however, by no means devoid of beauty. The sea of roofs of a great city has much of the grandeur of the ocean about it. To begin with, the streets in their crossings and windings cut up the mass into a hundred charming figures, streaming out from the Halles like the rays of a star. The streets of Saint-Denis and Saint-Martin, with their innumerable ramifications, went up side by side like two great trees intertwining their branches; while such streets as the Rue de la Platerie, Rue de la Verrerie, Rue de la Tixeranderie, etc., wound in tortuous lines through the whole. Some handsome edifices, too, thrust up their heads through the petrified waves of this sea of gables. For instance, at the head of the Pont-aux-Changeurs, behind which you could see the Seine foaming under the mill-wheels of the Pont-aux-Meuniers, there was the Chatelet, no longer a Roman keep, as under Julian the Apostate, but a feudal tower of the thirteenth century, and built of stone so hard that three hours work with the pick did not remove more than the size of a mans fist. Then there was the square steeple of Saint-Jacques-de-la-Boucherie, with its richly sculptured corners, most worthy of admiration even then, though it was not completed in the fifteenth century; it lacked in particular the four monsters which, still perched on the four corners of its roof, look like sphinxes offering to modern Paris the enigma of the old to unriddle. Rault, the sculptor, did not put them up till 1526, and received twenty francs for his trouble. There was the Maison-aux-Piliers, facing the Place de Grve, of which we have already given the reader some idea; there was Saint-Gervais, since spoilt by a doorway in good taste; Saint-Mry, of which the primitive pointed arches were scarcely more than circular; Saint-Jean, whose magnificent spire was proverbial and twenty other edifices which disdained not to hide their wonders in that chaos of deep, dark, narrow streets. Add to these the carved stone crosses, more numerous at the crossways than even the gibbets; the cemetery of the Innocents, of whose enclosing wall you caught a glimpse in the distance; the pillory of the Halles, just visible between two chimneys of the Rue de la Cossonnerie; the gibbet of the Croix du Trahoir at the corner of the ever-busy thoroughfare; the round stalls of the Corn Market; fragments of the old wall of Philip Augustus, distinguishable here and there, buried among the houses; mouldering, ivyclad towers, ruined gateways, bits of crumbling walls; the quay with its myriad booths and gory skinning yards; the Seine, swarming with boats from the Port au Foin or hay wharf to the For-1Evque, and you will be able to form some adequate idea of what the great irregular quadrangle of the Town looked like in 1482. -Besides these two quartersthe one of palaces, the other of housesthe Town contributed a third element to the view: that of a long belt of abbeys which bordered almost its entire circumference from east to west; and, lying just inside the fortified wall which encircled Paris, furnished a second internal rampart of cloisters and chapels. Thus, immediately adjoining the park of the Tournelles, between the Rue Saint-Antoine and the old Rue du Temple, stood the old convent of Sainte-Catherine, with its immense grounds, bounded only by the city wall. Between the old and the new Rue du Temple was the Temple itself, a grim of lofty towers, standing haughty and alone, surrounded by a vast, embattled wall. Between the Rue Neuve du Temple and the Rue Saint-Martin, in the midst of gardens, stood the Abbey of Saint-Martin a superb fortified church, whose girdle of towers and crown of steeples were second only to Saint-Germain-des-Prs in strength and splendour. -Between the two streets of Saint-Martin and Saint-Denis stretched the convent enclosure of the Trinit, and between the Rue Saint-Denis and the Rue Montorgueil that of Filles-Dieu. Close by, one caught a glimpse of the mouldering roofs and broken wall of the Cour des Miracles, the only profane link in that pious chain. -ly, the fourth area, standing out distinctly in the conglomeration of roofs on the right bank, and occupying the eastern angle formed by the city wall and the river wall, was a fresh knot of palaces and mansions clustered round the foot of the Louvre. The old Louvre of Philip Augustus, that stupendous pile whose enormous middle tower mustered round it twenty-three major towers, irrespective of the smaller ones, appeared from the distance as if encased within the Gothic roof-lines of the H?tel dAlen?on and the Petit-Bourbon. This hydra of towers, this guardian monster of Paris, with its twenty-four heads ever erect, the tremendous ridge of its roof sheathed in lead or scales of slate and glistening in metallic lustre, furnished an unexpected close to the western configuration of the Town. -This, then, was the town of Paris in the fifteenth centuryan immense masswhat the Romans called insulaof burgher dwelling-houses, flanked on either side by two blocks of palaces, terminated the one by the Louvre, the other by the Tournelles, bordered on the north by a long chain of abbeys and walled gardens all blended and mingling in one harmonious whole; above these thousand buildings with their fantastic outline of tiled and slated roofs, the steeplesfretted, fluted, honeycombedof the forty-four churches on the right bank; myriads of streets cutting through it; as boundary: on one side a circuit of lofty walls with square towers (those of the University wall were round); on the other, the Seine, intersected by bridges and carrying numberless boats. -Beyond the walls a few suburbs hugged the protection of the gates, but they were less numerous and more scattered than on the side of the University. In the rear of the Bastile about twenty squalid cottages huddled round the curious stonework of the Croix-Faubin, and the abutments of the Abbey of Saint-Antoine des Champs; then came Popincourt, buried in cornfields; then La Courtille, a blithe village of taverns; the market-town of Saint-Laurent with its church steeple appearing in the distance as if one of the pointed towers of the Porte Saint-Martin; the suburb of Saint-Denis with the vast enclosure of Saint-Ladre; outside the Porte-Montmartre, the Grange-Batelire encircled by white walls; behind that again, with its chalky slopes, Montmartre, which then had almost as many churches as wind-mills, but has only retained the wind-mills, for the world is now merely concerned for bread for the body. Finally, beyond the Louvre, among the meadows, stretched the Faubourg Saint-Honor, already a considerable suburb, and the verdant pastures of Petite-Bretagne and the March-aux-Porceaux or pig-market, in the middle of which stood the horrible furnace where they seethed the false coiners. -On the top of a hill, rising out of the solitary plain between La Courtille and Saint-Laurent, you will have remarked a sort of building, presenting the appearance, in the distance, of a ruined colonnade with its foundation laid bare. But this was neither a Pantheon nor a Temple of Jupiter; it was Montfaucon. 6 -Now, if the enumeration of so many edifices, brief as we have done our best to make it, has not shattered in the readers mind the image of old Paris as fast as we have built it up, we will recapitulate in a few words. In the centre, the island of the City like an immense tortoise, stretching out its tiled bridges like scaly paws from under its gray shell of roofs. On the left, the dense, bristling, square block of the University; on the right, the high semicircle of the Town, showing many more gardens and isolated edifices than the other two. The three areas, City, University, and Town, are veined with streets innumerable. Athwart the whole runs the Seine the fostering Seine, as Peter du Breul calls itencumbered with islands, bridges, and boats. All around, a vast plain checkered with a thousand forms of cultivation and dotted with fair villages; to the left, Issy, Vanvres, Vaugirarde, Montrouge, Gentilly, with its round and its square tower, etc.; to the right, a score of others from Conflans to Ville-lEvque; on the horizon, a border of hills ranged in a circle, the rim of the basin, as it were. Finally, far to the east, Vincennes with its seven square towers; southward, Bictre and its sharp-pointed turrets; northward, Saint-Denis with its spire; and in the west, Saint-Cloud and its castle-keep. Such was the Paris which the ravens of 1482 looked down upon from the heights of Notre-Dame. -And yet this was the city of which Voltaire said that before the time of Louis XIV it only possessed four handsome examples of architecturethe dome of the Sorbonne, the Val-de-Grace, the modern Louvre, and I forget the fourththe Luxembourg, perhaps. Fortunately, Voltaire was none the less the author of Candide; and none the less the man of all others in the long line of humanity who possessed in highest perfection the rire diaboliquethe sardonic smile. It proves, besides, that one may be a brilliant genius, and yet know nothing of an art one has not studied. Did not Molire think to greatly honour Raphael and Michael Angelo by calling them the Mignards 7 of their age? -But to return to Paris and the fifteenth century. -It was in those days not only a beautiful city; it was a homogeneous city, a direct productarchitectural and historicalof the Middle Ages, a chronicle in stone. It was a city composed of two architectural strata onlythe Romanesque and the Gothicfor the primitive Roman layer had long since disappeared excepting in the Baths of Julian, where it still pierced through the thick overlying crust of the Middle Ages. As for the Celtic stratum, no trace of it was discoverable even when sinking wells. -Fifty years later, when the Renaissance came, and with that unity of style, so severe and yet so varied, associated its dazzling wealth of fantasy and design, its riot of Roman arches, Doric columns and Gothic vaults, its delicate and ideal sculpture, its own peculiar tastes in arabesques and capitals, its architectural paganism contemporary with Luther, Paris was perhaps more beautiful still though less harmonious to the eye and the strictly artistic sense. But that splendid period was of short duration. The Renaissance was not impartial; it was not content only to erect, it must also pull down; to be sure, it required space. Gothic Paris was complete but for a moment. Scarcely was Saint-Jacques-de-la-Boucherie finished when the demolition of the old Louvre began. -Since then the great city has gone on losing her beauty day by day. The Gothic Paris, which was effacing the Romanesque, had been effaced in its turn. But what name shall be given to the Paris which has replaced it? -We have the Paris of Catherine de Mdicis in the Tuileries; the Paris of Henri II in the H?tel-de-Ville, both edifices in the grand style; the Place Royale shows us the Paris of Henri IVbrick fronts, stone copings, and slate roofstricolour houses; the Val-de-Grace is the Paris of Louis XIIIlow and broad in style, with basket-handle arches and something indefinably pot-bellied about its pillars and humpbacked about its domes. We see the Paris of Louis XIV in the Invalidesstately, rich, gilded, cold; the Paris of Louis XV at Saint-Sulpicescrolls and love-knots and clouds, vermicelli and chicory leavesall in stone; the Paris of Louis XVI in the Panthon, a bad copy of Saint Peters at Rome (the building has settled rather crookedly, which has not tended to improve its lines); the Paris of the Republic at the School of Medicinea spurious hash of Greek and Roman, with about as much relation to the Coliseum or the Panthon as the constitution of the year III has to the laws of Minosa style known in architecture as the Messidor; 8 the Paris of Napoleon in the Place Vend?mea sublime idea, a bronze column made of cannons; the Paris of the Restoration at the Boursean abnormally white colonnade supporting an abnormally smooth friezeit is perfectly square and cost twenty million francs. -To each of these characteristic buildings there belongs, in virtue of a similarity of style, of form, and of disposition, a certain number of houses scattered about the various districts easily recognised and assigned to their respective dates by the eye of the connoisseur. To the seeing eye, the spirit of a period and the features of a King are traceable even in the knocker of a door. -The Paris of to-day has, therefore, no typical characteristic physiognomy. It is a collection of samples of several periods, of which the finest have disappeared. The capital is increasing in houses only, and what houses! At this rate, there will be a new Paris every fifty years. The historic significance, too, of its architecture is lessened day by day. The great edifices are becoming fewer and fewer, are being swallowed up before our eyes by the flood of houses. Our fathers had a Paris of stone; our sons will have a Paris of stucco. -As for the modern structures of this new Paris, we would much prefer not to dilate upon them. Not that we fail to give them their due. The Sainte-Genevive of M. Soufflot is certainly the finest tea-cake that ever was made of stone. The palace of the Lgion dHonneur is also a most distinguished piece of confectionery. The dome of the Corn Market is a jockey-cap set on the top of a high ladder. The towers of Saint-Sulpice are two great clarinetsa shape which is as good as any otherand the grinning zigzag of the telegraph agreeably breaks the monotony of their roofs. Saint-Roch possesses a door that can only be matched in magnificence by that of Saint Thomas Aquinas; also it owns a Calvary in alto-relievo down in a cellar, and a monstrance of gilded woodreal marvels these, one must admit. The lantern tower in the maze at the Botanical Gardens is also vastly ingenious. As regards the Bourse, which is Greek as to its colonnade, Roman as to the round arches of its windows and doors, and Renaissance as to its broad, low, vaulted roof, it is indubitably in purest and most correct style; in proof of which we need only state that it is crowned by an attic story such as was never seen in Athensa beautiful straight line, gracefully intersected at intervals by chimney pots. And, admitting that it be a rule in architecture that a building should be so adapted to its purpose that that purpose should at once be discernible in the aspect of the edifice, no praise is too high for a structure which might, from its appearance, be indifferently a royal palace, a chamber of deputies, a town hall, a college, a riding-school, an academy, a warehouse, a court of justice, a museum, a barracks, a mausoleum, a temple, or a theatreand all the time it is an Exchange. Again, a building should be appropriate to the climate. This one is obviously constructed for our cold and rainy skies. It has an almost flat roof, as they obtain in the East, so that in winter, when it snows, that roof has to be swept, and, of course, we all know that roofs are intended to be swept. And as regards the purpose of which we spoke just now, the building fulfils it to admiration: it is a Bourse in France as it would have been a Temple in Greece. It is true that the architect has been at great pains to conceal the face of the clock, which would have spoilt the pure lines of the fa?ade; but in return, we have the colonnade running round the entire building, under which, on high-days and holidays, the imposing procession of stock-brokers and exchange-agents can display itself in all its glory. -These now are undoubtedly very superior buildings. Add to them a number of such handsome, interesting, and varied streets as the Rue de Rivoli, and I do not despair of Paris offering one day to the view, if seen from a balloon, that wealth of outline, that opulence of detail, that diversity of aspect, that indescribable air of grandeur in its simplicity, of the unexpected in its beauty, which characterizesa draught-board. -Nevertheless, admirable as the Paris of to-day may seem to you, conjure up the Paris of the fifteenth century; rebuild it in imagination; look through that amazing forest of spires, towers, and steeples; pour through the middle of the immense city the Seine, with its broad green and yellow pools that make it iridescent as a serpents skin; divide it at the island points, send it swirling round the piers of the bridges; project sharply against an azure horizon the Gothic profile of old Paris; let its outline float in a wintry mist clinging round its numerous chimneys; plunge it in deepest night, and watch the fantastic play of light and shadow in that sombre labyrinth of edifices; cast into it a ray of moonlight, showing it vague and uncertain, with its towers rearing their massive heads above the mists; or go back to the night scene, touch up the thousand points of the spires and gables with shadow, let it stand out more ridged and jagged than a sharks jaw against a coppery sunset skyand then compare. -And if you would receive from the old city an impression the modern one is incapable of giving, go at dawn on some great festivalEaster or Whitsuntideand mount to some elevated point, whence the eye commands the entire capital, and be present at the awakening of the bells. Watch, at a signal from heavenfor it is the sun that gives itthose thousand churches starting from their sleep. First come scattered notes passing from church to church, as when musicians signal to one another that the concert is to begin. Then, suddenly beholdfor there are moments when the ear, too, seems to have sightbehold, how, at the same moment, from every steeple there rises a column of sound, a cloud of harmony. At first the vibration of each bell mounts up straight, pure, isolated from the rest, into the resplendent sky of morn; then, by degrees, as the waves spread out, they mingle, blend, unite one with the other, and melt into one magnificent concert. Now it is one unbroken stream of sonorous sound poured incessantly from the innumerable steeplesfloating, undulating, leaping, eddying over the city, the deafening circle of its vibration extending far beyond the horizon. Yet this scene of harmony is no chaos. Wide and deep though it be, it never loses its limpid clearness; you can follow the windings of each separate group of notes that detaches itself from the peal; you can catch the dialogue, deep and shrill by turns, between the bourdon and the crecelle; you hear the octaves leap from steeple to steeple, darting winged, airy, strident from the bell of silver, dropping halt and broken from the bell of wood. You listen delightedly to the rich gamut, incessantly ascending and descending, of the seven bells of Saint-Eustache; clear and rapid notes flash across the whole in luminous zigzags, and then vanish like lightning. That shrill, cracked voice over there comes from the Abbey of Saint-Martin; here the hoarse and sinister growl of the Bastile; at the other end the boom of the great tower of the Louvre. The royal carillon of the Palais scatters its glittering trills on every side, and on them, at regular intervals, falls the heavy clang of the great bell of Notre-Dame, striking flashes from them as the hammer from the anvil. At intervals, sounds of every shape pass by, coming from the triple peal of Saint-Germain-des-Prs. Then, ever and anon, the mass of sublime sound opens and gives passage to the stretto of the Ave-Maria chapel, flashing through like a shower of meteors. Down below, in the very depths of the chorus, you can just catch the chanting inside the churches, exhaled faintly through the pores of their vibrating domes. Here, in truth, is an opera worth listening to. In general, the murmur that rises up from Paris during the daytime is the city talking; at night it is the city breathing; but this is the city singing. Lend your ear, then, to this tutti of the bells; diffuse over the ensemble the murmur of half a million of human beings, the eternal plaint of the river, the ceaseless rushing of the wind, the solemn and distant quartet of the four forests set upon the hills, round the horizon, like so many enormous organ-cases; muffle in this, as in a sort of twilight, all of the great central peal that might otherwise be too hoarse or too shrill, and then say whether you know of anything in the world more rich, more blithe, more golden, more dazzling, than this tumult of bells and chimesthis furnace of music, these ten thousand brazen voices singing at once in flutes of stone, three hundred feet highthis city which is now but one vast orchestrathis symphony with the mighty uproar of a tempest. -______________________ -1 This might be freely translated: The dam damming Paris, sets Paris damning. -2 Portions of these Roman baths still exist in the H?tel de Cluny. -3 The recreation and fighting ground of the students, the present Faubourg Saint-Germain. -4 Fidelity to the kings, though broken at times by revolts, procured the burghers many privileges. -5 An order formed in the twelfth century, specially vowed to the rescuing of Christians out of slavery. -6 The place of execution, furnished with immense gibbets, the site of an ancient Druidical temple. -7 Pierre Mignard (1610C1695), the well-known French painter, a contemporary of Molire. -8 From that period of the French Revolution when this bad imitation of the antique was much in vogue. -BOOK IV -Chapter 1 - Charitable Souls -Sixteen years before the events here recorded took place, early on Quasimodo or Low-Sunday morning, a human creature had been deposited after Mass on the plank bed fastened to the pavement on the left of the entrance to Notre-Dame, opposite the great image of Saint Christopher, which the kneeling stone figure of Messire Antoine des Essarts, knight, had contemplated since 1413. Upon this bed it was customary to expose foundling children to the charity of the public; any one could take them away who chose. In front of the bed was a copper basin for the reception of alms. -The specimen of humanity lying on this plank on the morning of Quasimodo-Sunday, in the year of our Lord 1467, seemed to invite, in a high degree, the curiosity of the very considerable crowd which had collected round it. This crowd was largely composed of members of the fair sex; in fact, there were hardly any but old women. -In front of the row of spectators, stooping low over the bed, were four of them whom by their gray cagoulesa kind of hooded cassockone recognised as belonging to some religious order. I see no reason why history should not hand down to posterity the names of these discreet and venerable dames. They were: Agns la Herme, Jehanne de la Tarme, Henriette la Gaultire, and Gauchre la Violetteall four widows, all four bedes-women of the Chapelle tienne-Haudry, who, with their superiors permission, and conformably to the rules of Pierre dAilly, had come to hear the sermon. -However, if these good sisters were observing for the moment the rules of Pierre dAilly, they were certainly violating to their hearts content those of Michel de Brache and the Cardinal of Pisa, which so inhumanly imposed silence upon them. -What can that be, sister? said Agns la Gauchre as she gazed at the little foundling, screaming and wriggling on its wooden pallet, terrified by all these staring eyes. -What are we coming to, said Jehanne, if this is the kind of children they bring into the world now? -I am no great judge of children, resumed Agns, but it must surely be a sin to look at such a one as this. -Its not a child, Agns. -Its a monkey spoiled, observed Gauchre. -Its a miracle, said Henriette la Gaultire. -If so, remarked Agns, it is the third since L?tare Sunday, for it is not a week since we had the miracle of the mocker of pilgrims suffering divine punishment at the hands of Our Lady of Aubervilliers, and that was already the second within the month. -But this so-called foundling is a perfect monster of abomination, said Jehanne. -He bawls loud enough to deafen a precentor, continued Gauchre. Hold your tongue, you little bellower! -And to say that the Bishop of Reims sent this monstrosity to the Bishop of Paris! exclaimed Gaultire, clasping her hands. -I expect, said Agns la Herme, that it is really a beast of some sort, an animalthe offspring of a Jew and a sow; something, at any rate, that is not Christian, and that ought to be committed to the water or the fire. -Surely, went on La Gaultire, nobody will have anything to do with it. -Oh, mercy! cried Agns, what if those poor nurses at the foundling-house at the bottom of the lane by the river, close beside the Lord Bishopswhat if they take this little brute to them to be suckled! I would rather give suck to a vampire. -What a simpleton she is, that poor La Herme! returned Jehanne; dont you see, ma s?ur, that this little monster is at least four years old, and that a piece of meat would be more to his taste than your breast? -And in truth the little monster (for we ourselves would be at a loss to describe it by any other name) was not a newborn babe. It was a little angular, wriggling lump, tied up in a canvas sack marked with the monogram of Messire Guillaume Chartier, the then Bishop of Paris, with only its head sticking out at one end. But what a head! All that was visible was a thatch of red hair, an eye, a mouth, and some teeth. The eye wept, the mouth roared, and the teeth seemed only too ready to bite. The whole creature struggled violently in the sack, to the great wonderment of the crowd, constantly increasing and collecting afresh. -The Lady Alo?se de Gondelaurier, a wealthy and noble dame, with a long veil trailing from the peak of her head-dress, and holding by the hand a pretty little girl of about six years of age, stopped in passing and looked for a moment at the hapless creature, while her charming little daughter, Fleur-de-Lys de Gondelaurier, all clad in silks and velvets, traced with her pretty finger on the permanent tablet attached to the bed the words: Enfants trouvs. -Good lack! said the lady, turning away in disgust. I thought they exposed here nothing but babes. -And she went on her way, first, however, tossing a silver florin into the basin among the coppers, causing the eyes of the poor sisters of the Chapelle tienne-Haudry to open wide with astonishment. -A moment afterward the grave and learned Robert Mistricolle, protonotary to the King, came along, with an enormous missal under one arm, and on the other his wife (Dame Guillemette la Mairesse), having thus at his side his two monitors the spiritual and the temporal. -Foundling! said he, after examining the object. Found evidently on the brink of the river Phlegethon. -You can see but one eye, observed Dame Guillemette. There is a wart over the other. -That is no wart, returned Ma?tre Robert Mistricolle. That is an egg containing just such another demon, which has a similar little egg with another little devil inside it, and so on. -How do you know that? asked Dame Guillemette. -I know it for a fact, replied the protonotary. -Monsieur the protonotary, asked Gauchre, what do you predict from this pretended foundling? -The greatest calamities, returned Mistricolle. -Ah, mon Dieu! cried an old woman among the by-standers, and there was already a considerable pestilence last year, and they say that the English are prepared to land in great companies at Harfleur. -Maybe that will prevent the Queen coming to Paris in September, remarked another, and trade is bad enough as it is. -Its my opinion, cried Jehanne de la Tarme, that it would be better for the people of Paris if this little wizard were lying on a bundle of fagots instead of a bed. -And nice blazing fagots too, added the old woman. -It would be wiser, said Mistricolle. -For some moments past a young priest, stern of face, with a broad forehead and penetrating eye, had stood listening to the argument of the Haudriette sisters, and the pronouncements of the protonotary. He now silently parted the crowd, examined the little wizard, and stretched a hand over him. It was high time, for these pious old women were already licking their lips in anticipation of the fine blazing fagots. -I adopt this child, said the priest. -He wrapped it in his soutane and carried it off, the by-standers looking after him in speechless amazement. The next moment he had disappeared through the Porte Rouge, which led at that time from the church into the cloister. -The first shock of surprise over, Jehanne de la Tarme bent down and whispered in the ear of La Gaultire: Did I not say to you, ma s?ur, that that young cleric, M. Claude Frollo, was a sorcerer? -Chapter 2 - Claude Frollo -In truth, Claude Frollo was no ordinary person. -He belonged to one of those families which it was the foolish fashion of the last century to describe indifferently as the upper middle class or lower aristocracy. -The family had inherited from the brothers Paclet the fief of Tirechappe, which was held of the Bishop of Paris, and the twenty-one houses of which had, since the thirteenth century, been the object of countless litigations in the Ecclesiastical Court. As owner of this fief, Claude Frollo was one of the seven times twenty-one seigneurs claiming manorial dues in Paris and its suburbs; and in that capacity his name was long to be seen inscribed between the H?tel de Tancarville, belonging to Ma?tre Franc?ois le Rez, and the College of Tours, in the cartulary deposited at Saint-Martin des Champs. -From his childhood Claude Frollo had been destined by his parents for the priesthood. He had been taught to read in Latin; he had early been trained to keep his eyes downcast, and to speak in subdued tones. While still quite a child his father had bound him to the monastic seclusion of the Collge de Torchi in the University, and there he had grown up over the missal and the lexicon. -He was, however, by nature a melancholy, reserved, serious boy, studying with ardour and learning easily. He never shouted in the recreation hour; he mixed but little in the bacchanalia of the Rue du Fouarre; did not know what it was to dare alapas et capillos laniare,1 and had taken no part in that Students riot of 1463, which the chroniclers gravely record as The Sixth Disturbance in the University. It rarely happened that he jibed at the poor scholars of Montaigu for their cappettes, from which they derived their nickname, or the exhibitioners of the Collge de Dormans for their smooth tonsure and their tricoloured surcoats of dark blue, light blue and violet clothazurini coloris et bruni, as the charter of the Cardinal des Quatre-Couronnes puts it. -On the other hand, he was assiduous in his attendance at the higher and lower schools of the Rue Saint-Jean de Beauvais. The first scholar whom the Abb de Saint-Pierre de Val caught sight of, established against a pillar in the cole Saint-Vendregesile, exactly opposite to his desk when he began his lecture on Canon Law, was invariably Claude Frollo, armed with his inkhorn, chewing his pen, scribbling on his threadbare knees, or, in winter, blowing on his fingers. The first pupil Messire Miles dIsliers, doctor of ecclesiastical law, saw arrive breathless every Monday morning as the door of the Chef-Saint-Denis schools opened, was Claude Frollo. Consequently, by the time he was sixteen, the young cleric was a match in mystical theology for a Father of the Church, and in scholastic theology for a Doctor of the Sorbonne. -Having finished with theology, he threw himself into canonical law and the study of the decretals. -From the Magister Sententiarum he had fallen upon the Capitularies of Charlemagne, and in his insatiable hunger for knowledge had devoured decretal after decretal: those of Theodore, Bishop of Hispalis, those of Bouchard, Bishop of Worms, those of Yves, Bishop of Chartres; then the decretal of Gratian, which came after Charlemagnes Capitularies; then the collection of Gregory IX; then the epistle Super specula of Honorius III. He thoroughly investigated and made himself familiar with that vast and stormy period of bitter and protracted struggle between Civil and Ecclesiastical Law during the chaos of the Middle Ages, a period which Bishop Theodore began in 618, and Pope Gregory closed in 1227. -The decretals assimilated, he turned his attention to medicine and the liberal arts; studied the science of herbs and of salves; became an expert in the treatment of fevers and contusions, of wounds and of abscesses. Jacques dEspars would have passed him as physician; Richard Hellain, as surgeon. He ran through the degrees of Licentiate, Master, and Doctor of Arts; he studied languages: Latin, Greek, and Hebrew a thrice inner sanctuary of learning seldom penetrated at that time. He was possessed by a veritable rage for acquiring and storing up knowledge. At eighteen, he had made his way through the four faculties. Life for this young man seemed to have but one aim and objectknowledge. -It was just about this time that the excessive heat of the summer of 1466 caused the outbreak of that great pestilence which carried off more than forty thousand people in the jurisdiction of Paris, among others, says Jean de Troyes, Ma?tre Arnoul, the Kings astrologer, a right honest man, both wise and merry withal. The rumour spread through the University that the Rue Tirechappe had been specially devastated by the malady. It was here, in the middle of their fief, that Claudes parents dwelt. Much alarmed, the young student hastened forthwith to his fathers house, only to find that both father and mother had died the previous day. An infant brother, in swaddling-clothes, was still alive and lay wailing and abandoned in the cradle. This was all that remained to Claude of his family. The young man took the child in his arms and went thoughtfully away. Hitherto he had lived only in the world of Learning; now he was to begin living in the world of Life. -This catastrophe was a turning point in Claude Frollos existence. An orphan, an elder brother, and the head of his house at nineteen, he felt himself rudely recalled from the reveries of the school to the realities of the world. It was then that, moved with pity, he was seized with a passionate devotion for this infant brother. How strange and sweet a thing this human affection to him, who had never yet loved aught but books! -This affection waxed strong to a singular degree; in a soul so new to passion, it was like a first love. Separated since his childhood from his parents whom he had scarcely known; cloistered and immured, as it were, in his books, eager before all things to study, to learn; attentive hitherto only to his intellect which expanded in science, to his imagination which grew with his literary studies, the poor scholar had not yet had time to feel that he had a heart. This young brother, without mother or father, this helpless babe, suddenly fallen from the skies into his arms, made a new man of him. He perceived for the first time that there were other things in the world besides the speculations of the Sorbonne and the verses of Homer; that Man has need of the affections; that life without tenderness and without love is a piece of heartless mechanism, insensate, noisy, wearisome. Only, he imagined, being as yet at the age when one illusion is replaced merely by another illusion, that the affections of blood and kindred were the only ones necessary, and the love for a little brother was sufficient to fill his whole existence. -He threw himself, therefore, into the love of his little Jehan with all the passion of a character already profound, ardent, and concentrated. The thought of this poor, pretty, rosy, golden-haired creature, this orphan with another orphan for its sole support, moved him to the hearts core, and like the earnest thinker that he was, he began to reflect upon Jehan with a sense of infinite compassion. He lavished all his solicitude upon him as upon something very fragile, very specially recommended to his care. He became more than a brother to the babe: he became a mother. -Little Jehan having still been at the breast when he lost his mother, Claude put him out at nurse. Besides the fief of Tirechappe, he inherited from his father that of Moulin, which was held of the square tower of Gentilly. It was a mill standing upon rising ground, near the Castle of Winchestre, the present Bictre. The millers wife was suckling a fine boy at the time; the mill was not far from the University, and Claude carried his little Jehan to her himself. -Thenceforward, feeling he had a heavy responsibility on his shoulders, he took life very seriously. The thought of his little brother not only became his recreation from study, but the chief object of those studies. He resolved to devote himself wholly to the future of that being for whom he was answerable before God, and never to have any other spouse, any other child than the happiness and welfare of his little brother. -He bound himself, therefore, still more closely to his clerical vocation. His personal merits, his learning, his position as an immediate vassal of the Bishop of Paris, opened wide to him the doors of the Church. At twenty, by special dispensation from the Holy See, he was ordained priest, and as the youngest of the chaplains of Notre-Dame, performed the service at the altar called, from the late hour at which the mass was celebrated there, altare pigrorumthe sluggards altar. -After this, and because he was more than ever immersed in his beloved books, which he only left to hasten for an hour to the mill, this union of wisdom and austerity, so rare at his age, had speedily gained him the respect and admiration of the cloister. From the cloister his fame for erudition had spread to the people, by whom, as frequently happened in those days, it had been converted in some sort into a reputation for necromancy. -It was just as he was returning on Quasimodo-Sunday from celebrating mass for the sluggards at their altarwhich was beside the door in the choir leading into the nave, on the right, near the image of the Virginthat his attention had been arrested by the group of old women chattering round the foundling. -He accordingly drew nearer to the poor little creature, the object of so much abhorrence and ill-will. The sight of its distress, its deformity, its abandonment, the remembrance of his young brother, the horror that suddenly assailed him at the thought that if he were to die his beloved little Jehan might thus be miserably exposed upon the selfsame bedall this rushed into his mind at once, and, moved by an impulse of profound compassion, he had carried away the child. -When he took the child out of the sack, he found it was indeed ill-favoured. The poor little wretch had a great wart over the left eye, its head was sunk between its shoulders, the spine arched, the breastbone protruding, the legs bowed. Yet he seemed lively enough; and although it was impossible to make out the language of his uncouth stammerings, his voice evidenced a fair degree of health and strength. Claudes compassion was increased by this ugliness, and he vowed in his heart to bring up this child for love of his brother; so that, whatever in the future might be the faults of little Jehan, this good deed, performed in his stead, might be accounted to him for righteousness. It was a sort of investment in charity effected in his brothers name, a stock of good works laid up for him in advance, on which the little rogue might fall back if some day he found himself short of that peculiar form of small changethe only kind accepted at the Gate of Heaven. -He christened his adopted child by the name of Quasimodo, either to commemorate thereby the day on which he found him, or to indicate by that name how incomplete and indefinite of shape the unfortunate little creature was. And, in truth, one-eyed, humpbacked, bow-legged, poor Quasimodo could hardly be accounted more than quasi human. -______________________ -1 Deal out cuffs on the head and fight. -Chapter 3 - Immanis Pecoris Custos, Immanior IPSE1 -Now, by 1482, Quasimodo had come to mans estate, and had been for several years bell-ringer at Notre-Dame, by the grace of his adopted father, Claude Frollowho had become archdeacon of Josas, by the grace of his liege lord, Louis de Beaumontwho, on the death of Guillaume Chartier in 1472, had become Bishop of Paris, by the grace of his patron, Olivier le Daim, barber to Louis XI, King by the grace of God. -Quasimodo then was bell-ringer of Notre-Dame. -As time went on a certain indescribable bond of intimacy had formed between the bell-ringer and the church. Separated forever from the world by the double fatality of his unknown birth and his actual deformity, imprisoned since his childhood within those two impassable barriers, the unfortunate creature had grown accustomed to taking note of nothing outside the sacred walls which had afforded him a refuge within their shade. Notre-Dame had been to him, as he grew up, successively the egg, the nest, his home, his country, the universe. -Certain it is that there was a sort of mysterious and pre-existent harmony between this being and this edifice. When, as a quite young child, he would drag himself about with many clumsy wrigglings and jerks in the gloom of its arches, he seemed, with his human face and beast-like limbs, the natural reptile of that dark and humid stone floor, on which the shadows of the Roman capitals fell in so many fantastic shapes. -And later, the first time he clutched mechanically at the bell-rope in the tower, clung to it and set the bell in motion, the effect to Claude, his adopted father, was that of a child whose tongue is loosened and begins to talk. -Thus, as his being unfolded itself gradually under the brooding spirit of the Cathedral; as he lived in it, slept in it, rarely went outside its walls, subject every moment to its mysterious influence, he came at last to resemble it, to blend with it and form an integral part of it. His salient angles fitted, so to speak, into the retreating angles of the edifice till he seemed not its inhabitant, but its natural tenant. He might almost be said to have taken on its shape, as the snail does that of its shell. It was his dwelling-place, his strong-hold, his husk. There existed between him and the ancient church so profound an instinctive sympathy, so many material affinities, that, in a way, he adhered to it as a tortoise to his shell. The hoary Cathedral was his carapace. -Needless to say, the reader must not accept literally the similes we are forced to employ in order to express this singular unionsymmetrical, direct, consubstantial almostbetween a human being and an edifice. Nor is it necessary to describe how minutely familiar he had become with every part of the Cathedral during so long and so absolute an intimacy. This was his own peculiar dwelling-placeno depths in it to which Quasimodo had not penetrated, no heights which he had not scaled. Many a time had he crawled up the sheer face of it with no aid but that afforded by the uneven surface of the sculpture. The towers, over whose surface he might often be seen creeping like a lizard up a perpendicular wallthose two giants, so lofty, so grim, so dangeroushad for him no terrors, no threats of vertigo or falls from giddy heights; to see them so gentle between his hands, so easy to scale, you would have said that he had tamed them. By dint of leaping and climbing, of sportively swinging himself across the abysses of the gigantic Cathedral, he had become in some sort both monkey and chamois, or like the Calabrian child that swims before it can run, whose first play-fellow is the sea. -Moreover, not only his body seemed to have fashioned itself after the Cathedral, but his mind also. In what condition was this soul of his? What impressions had it received, what form had it adopted behind that close-drawn veil, under the influence of that ungentle life, it would be hard to say. Quasimodo had been born halt, humpbacked, half-blind. With infinite troubled and unwearied patience Claude Frollo had succeeded in teaching him to speak. But a fatality seemed to pursue the poor foundling. When, at the age of fourteen, he became a bell-ringer at Notre-Dame, a fresh infirmity descended on him to complete his desolation: the bells had broken the drum of his ears and he became stone-deaf. The only door Nature had left for him wide open to the world was suddenly closed forever. -And in closing it cut off the sole ray of joy and sunshine which still penetrated to the soul of Quasimodo, and plunged that soul into deepest night. The melancholy of the unhappy creature became chronic and complete like his physical deformity. Besides, his deafness rendered him in some sort dumb; for, to escape being laughed at, from the moment he found he could not hear, he firmly imposed upon himself a silence which he rarely broke except when he was alone. Of his own free-will, he tied that tongue which Claude Frollo had been at such pains to loosen. And hence it was that when necessity constrained him to speak, his tongue moved stiffly and awkwardly like a door on rusty hinges. -Were we to endeavour to pierce through that thick, hard rind and penetrate to Quasimodos soul; could we sound the depths of that misshapen organization; were it given to us to flash a torch into that rayless gloom, to explore the dark-some interior of that opaque structure, illumine its dim windings, its fantastic culs-de-sac, and suddenly throw a bright light on the Psyche chained in the innermost recesses of that cavern, we should doubtless find the hapless creature withered, stunted like those prisoners who grew old in the dungeons of Venice, bent double within the narrow limits of a stone chest too low and too short to permit of their stretching themselves. -It is certain that the spirit wastes in a misshapen body. Quasimodo scarcely felt within him the feeble stirrings of a soul made after his own image. His impression of objects suffered a considerable refraction before they reached his inner consciousness. His mind was a peculiar medium; the ideas that passed through it issued forth distorted. The reflection born of that refraction was necessarily divergent and crooked. -Hence his thousand optical illusions, hence the thousand aberrations of his judgment, the thousand vagaries of his thoughts, sometimes mad, sometimes idiotic. -The first effect of this fatal organization was to blur his view of things. He scarcely ever received a direct impression of them; the external world seemed to him much farther off than it does from us. -The second effect of his misfortune was to render him malevolent. He was malevolent really because he was uncivilized, and he was uncivilized because he was ill-favoured. There was method in his nature as well as in ours. -Also his physical strength, which was extraordinarily developed, was another cause of his malevolenceMalus puer robustus, 2 says Hobbes. -However, to do him justice, this malevolence was probably not inborn in him. From his very first experience among men, he had felt, and later he had seen, himself reviled, scorned, spat upon. For him human speech had ever been either a jibe or a curse. As he grew up, he had met nothing but disgust and ill-will on every side. What wonder that he should have caught the disease, have contracted the prevailing malice. He armed himself with the weapons that had wounded him. -But, after all, he turned his face unwillingly towards mankind. His Cathedral was sufficient for him. Was it not peopled with kings, saints, and bishops of marble who never mocked at him, but ever gazed at him with calm and benevolent eyes? And the other stone figuresthe demons and monstersthey showed no hatred of Quasimodohe looked too much akin to them for that. Rather they scoffed at other men. The saints were his friends and blessed him, the monsters were his friends and protected him. So he would commune long and earnestly with them, passing whole hours crouched in front of a statue, holding solitary converse with it. If any one happened upon him, he would fly like a lover surprised in a serenade. -And the Cathedral not only represented society; it was his world, it was all Nature to him. He dreamed of no other gardens but the stained windows ever in flower, no shade but that cast by the stone foliage spreading full of birds from the tufted capitals of the Roman pillars, no mountains but the colossal towers of the Cathedral, no ocean but Paris roaring round their base. -But what he loved best of all in that material edifice, that which awakened his soul and set the poor wings fluttering that lay so sadly folded when in that dreary dungeon, what brought him nearest to happiness, was the bells. He loved them, fondled them, talked to them, understood them. From the carillon in the transept steeple to the great bell over the central doorway, they all shared in his affection. The transept belfry and the two towers were to him three great cages, the birds in which, taught by him, would sing for him alone. Yet it was these same bells which had made him deaf; but mothers are often fondest of the child who has made them suffer most. -True, theirs were the only voices he could still hear. For this reason the great bell was his best beloved. She was his chosen one among that family of boisterous sisters who gambolled round him in high-days and holidays. This great bell was called Marie. She was alone in the southern tower with her sister Jacqueline, a bell of smaller calibre, hanging in a cage beside hers. This Jacqueline had been christened after the wife of Jean Montagu, who had given it to the churcha donation which had not prevented him from figuring at Montfaucon without his head. In the northern tower were six other bells, and six smaller ones shared the transept belfry with the wooden bell, which was only rung from the afternoon of Maundy Thursday till the morning of Easter eve. Quasimodo had thus fifteen bells in his seraglio, but big Marie was the favourite. What words shall describe his delight on the days when the full peal was rung? The moment the Archdeacon gave the word, he was up the spiral stair-case of the steeple quicker than any one else would have come down. He entered breathless into the aerial chamber of the great bell, gazed at her for a moment with doting fondness, then spoke softly to her and patted her as you would a good steed before starting on a long journey; sympathizing with her in the heavy task that lay before her. These preliminary caresses over, he called out to his assistants, waiting ready in the lower floor of the tower, to begin. These hung themselves to the ropes, the windlass creaked, and the huge metal dome set itself slowly in motion. Quasimodo, quivering with excitement, followed it with his eye. The first stroke of the clapper against its brazen wall shook the wood-work on which he was standing. Quasimodo vibrated with the bell. Vah! he shouted with a burst of insane laughter. Meanwhile the motion of the bell quickened, and in the same measure as it took a wider sweep, so the eye of Quasimodo opened more and more and blazed with a phosphorescent light. -At length the full peal began; the whole lower wood-work and blocks of stone trembled and groaned together from the piles of the foundation to the trefoils on its summit. Quasimodo, foaming at the mouth, ran to and fro, quivering with the tower from head to foot. The bell, now in full and furious swing, presented alternately to each wall of the tower its brazen maw, from which poured forth that tempestuous breath which could be heard four leagues distant. Quasimodo placed himself in front of this gaping throat, crouched down and rose again at each return of the bell, inhaled its furious breath, gazed in turn at the teeming square two hundred feet below and at the enormous brazen tongue which came at measured intervals to bellow in his ear. It was the only speech he understood, the only sound that broke for him the universal silence. He revelled in it like a bird in the sunshine. -Then, at a certain point, the frenzy of the bell would catch him; his expression grew strange and weird; waiting for the bell on its passage as a spider watches for the fly, he would fling himself headlong upon it. Then, suspended over the abyss, borne to and fro by the tremendous rush of the bell, he seized the brazen monster by its ears, pressed it between his two knees, dug his heels into it, and increased by the shock and the whole weight of his body the fury of the peal, till the tower rocked again. Meanwhile Quasimodo, shouting and gnashing his teeth, his red hair bristling, his chest heaving like a blacksmiths bellows, his eye darting flames, his monstrous steed neighing and panting under himit was no longer the great bell of Notre-Dame or Quasimodo, it was a nightmare, a whirlwind, a tempest; Vertigo astride of Clamour; a spirit clinging to a flying saddle; a strange centaur, half man, half bell; a sort of horrible Astolpho carried off by a prodigious living hippogriff of bronze. -The presence of this extraordinary being sent, as it were, a breath of life pulsing through the whole Cathedral. There seemed to emanate from himat least so said the exaggerating populacea mysterious influence which animated the stones of Notre-Dame and made the ancient church thrill to her deepest depths. To know that he was there was enough to make them believe they saw life and animation in the thousand statues of the galleries and portals. The old Cathedral did indeed seem docile and obedient to his hand; she awaited his command to lift up her sonorous voice; she was possessed and filled with Quasimodo as with a familiar spirit. You would have said that he made the immense building breathe. He was everywhere in it; he multiplied himself at every point of the structure. Now the terrified beholder would descry, on the topmost pinnacle of a tower, a fantastic, dwarfish figure climbing, twisting, crawling on all-fours, hanging over the abyss, leaping from projection to projection to thrust his arm down the throat of some sculptured gorgon: it was Quasimodo crows-nesting. Again, in some dim corner of the church one would stumble against a sort of living chimera crouching low, with sullen, furrowed brow: it was Quasimodo musing. Or again, in a steeple you caught sight of an enormous head and a bundle of confused limbs swinging furiously at the end of a rope: it was Quasimodo ringing for vespers or angelus. Often at night a hideous form might be seen wandering along the delicate and lace-like parapet that crowns the towers and borders the roof of the chancel: again the hunchback of Notre-Dame. At such times, said the gossips, the whole church assumed a horrible, weird, and supernatural air; eyes and mouths opened here and there; the stone dogs, the dragons, all the monsters that keep watch and ward, day and night, with necks distended and open mouths, round the huge Cathedral, were heard barking and hissing. And if it happened to be a Christmas-night when the great bell seemed to rattle in its throat as it called the faithful to the midnight mass, there was such an indescribable air of life spread over the sombre fa?ade that the great doorway looked as if it were swallowing the entire crowd, and the rose-window staring at them. And all this proceeded from Quasimodo. Egypt would have declared him the god of this temple; the Middle Ages took him for its demon: he was its soul. -So much so, that to any one who knows that Quasimodo really lived, Notre-Dame now appears deserted, inanimate, dead. One feels that something has gone out of it. This immense body is emptya skeleton; the spirit has quitted it; one sees the place of its habitation, but that is all. It is like a skullthe holes are there for the eyes, but they are sightless. -______________________ -1 The guardian of a terrific beast, himself more terrible. -2 The strong youth is wicked. -Chapter 4 - The Dog and his Master -There was, however, one human being whom Quasimodo excepted from the malice and hatred he felt for the rest of mankind, and whom he loved as much, if not more, than his Cathedral: and that was Claude Frollo. -The case was simple enough. Claude Frollo had rescued him, had adopted him, fed him, brought him up. When he was little, it was between Claude Frollos knees that he sought refuge from the children and the dogs that ran yelping after him. Claude Frollo had taught him to speak, to read, to write. Finally, it was Claude Frollo who made him bellringer of Notre-Dame; and to give the great bell in marriage to Quasimodo was giving Juliet to Romeo. -And in return, Quasimodos gratitude was deep, passionate, and boundless; and although the countenance of his adopted father was often clouded and severe, although his speech was habitually brief, harsh, imperious, never for one single moment did that gratitude falter. In Quasimodo the Archdeacon possessed the most submissive of slaves, the most obedient of servants, the most vigilant of watch-dogs. When the poor bell-ringer became deaf, between him and Claude Frollo there had been established a mysterious language of signs, intelligible to them alone. In this way, then, the Archdeacon was the sole human being with whom Quasimodo had preserved a communication. There were but two things in this world with which he had any connection: Claude Frollo and the Cathedral. -The empire of the Archdeacon over the bell-ringer, and the bell-ringers attachment to the Archdeacon, were absolutely unprecedented. A sign from Claude, or the idea that it would give him a moments pleasure, and Quasimodo would have cheerfully cast himself from the top of Notre-Dame. There was something remarkable in all that physical force, so extraordinarily developed in Quasimodo, being placed by him blindly at the disposal of another. In it there was doubtless much of filial devotion, of the attachment of the servant; but there was also the fascination exercised by one mind over another; it was a poor, feeble, awkward organism standing with bent head and supplicating eyes in the presence of a lofty, penetrating, and commanding intellect. Finally, and before all things, it was gratitudegratitude pushed to such extreme limits that we should be at a loss for a comparison. That virtue is not one of those of which the brightest examples are to be found in man. Let us then say that Quasimodo loved the Archdeacon as never dog, never horse, never elephant loved his master. -Chapter 5 - Further Particulars of Claude Frollo -In 1482 Quasimodo was about twenty, Claude Frollo about thirty-six. The one had grown up, the other had grown old. -Claude Frollo was no longer the simple-minded scholar of the Torchi College, the tender guardian of a little child, the young and dreamy philosopher, who knew many things, but was ignorant of more. He was a priestaustere, grave, morosehaving a cure of souls; Monsieur the Archdeacon of Josas; second acolyte to the Bishop; having the charge of the two deaneries of Montlhry and Chateaufort, and of a hundred and seventy-four rural clergy. He was an imposing and sombre personage, before whom the chorister boys in alb and tunic, the brethren of Saint-Augustine, and the clerics on early morning duty at Notre-Dame, quailed and trembled, when he passed slowly under the high Gothic arches of the choirstately, deep in thought, with folded arms, and his head bent so low upon his breast that nothing was visible of his face but his high bald forehead. -Dom1 Claude Frollo, however, had abandoned neither science nor the education of his young brotherthe two occupations of his life. But in the course of time some bitterness had mingled with these things he once had thought so sweet. With time, says Paul Diacre, even the best bacon turns rancid. Little Jehan Frollo, surnamed of the Mill from the place where he had been nursed, had not grown in the direction in which Claude would have wished to train him. The elder brother had counted on a pious pupil, docile, studious, and honourable. But the younger brother, like those young trees which baffle the efforts of the gardener, and turn obstinately towards that side from which they derive most air and sunshinethe younger brother increased and waxed great, and sent forth full and luxuriant branches only on the side of idleness, ignorance, and loose living. He was an unruly little devil, which made Dom Claude knit his brows, but also very droll and very cunning, at which the elder was fain to smile. Claude had consigned him to that same Collge de Torchi in which he himself had passed his earliest years in study and seclusion; and it grieved him sorely that this retreat, once edified by the name of Frollo, should be so scandalized by it now. He would sometimes read Jehan long and stern lectures on the subject, under which the latter bore up courageouslyafter all, the young rascals heart was in the right place, as all the comedies declare; but the sermon over, he calmly resumed the evil tenor of his ways. Sometimes it was a bjaune, or yellow-beak, as they called the new-comers at the Universitywhom he had thoroughly badgered as a welcomea valuable custom which has been carefully handed down to our day; now he had been the moving spirit of a band of scholars who had thrown themselves in classical fashion on a tavern, quasi classico excitati, then beaten the tavern-keeper with cudgels of offensive character, and joyously pillaged the tavern, even to staving in the hogsheads of wine. And the result was a fine report drawn up in Latin, brought by the sub-monitor of the Torchi College to Dom Claude, with piteous mien, the which bore the melancholy marginal remark, Rixa; prima causa vinum optimum potatum.2 Finally, it was saidhorrible in a lad of sixteenthat his backslidings frequently extended to the Rue de Glatigny.3 -In consequence of all this, Claudesaddened, his faith in human affection shakenthrew himself with frenzied ardour into the arms of science, that sister who at least never laughs at you in derision, and who always repays you, albeit at times in somewhat light coin, for the care you have lavished on her. He became, therefore, more and more erudite, and, as a natural consequence, more and more rigid as a priest, less and less cheerful as a man. In each of us there are certain parallels between our mind, our manners, and our characters which develop in unbroken continuity, and are only shaken by the great cataclysms of life. -Claude Frollo, having in his youth gone over the entire circle of human knowledge, positive, external, and lawful, was under the absolute necessity, unless he was to stop ubi defuit orbis,4 of going farther afield in search of food for the insatiable appetite of his mind. The ancient symbol of the serpent biting its tail is especially appropriate to learning, as Claude Frollo had evidently proved. Many trustworthy persons asserted that, after having exhausted the fas of human knowledge, he had the temerity to penetrate into the nefas, had tasted in succession all the apples of the Tree of Knowledge, and, whether from hunger or disgust, had finished by eating of the forbidden fruit. He had taken his seat by turns, as the reader has seen, at the conferences of the theologians at the Sorbonne, at the disputations of the decretalists near the image of Saint-Martin, at the meetings of the Faculty of Arts near the image of Saint-Hilary, at the confabulations of the physicians near the benitier of Notre-Dame, ad cupam Nostr?-Domin?; all the viands, permitted and approved, which those four great kitchens, called the four Faculties, could prepare and set before the intelligence, he had devoured, and satiety had come upon him before his hunger was appeased. Then he had penetrated farther afield, had dug deeper, underneath all that finite, material, limited knowledge; he had risked his soul, and had seated himself at that mystic table of the Alchemists, the Astrologers, the Hermetics of which Averro?s, Guillaume de Paris, and Nicolas Flamel occupy one end in the Middle Ages, and which reaches back in the East, under the rays of the seven-branched candlestick, to Solomon, Pythagoras, and Zoroaster. -So, at least, it was supposed, whether rightly or not. -It is certainly true that the Archdeacon frequently visited the cemetery of the Holy Innocents, where, to be sure, his mother and father lay buried with the other victims of the plague of 1466; but he seemed much less devoutly interested in the cross on their grave than in the strange figures covering the tombs of Nicolas Flamel and Claude Pernelle close by. -It is certainly true that he had often been seen stealing down the Rue des Lombards and slipping furtively into a little house which formed the corner of the Rue des Ecrivains and the Rue Marivault. This was the house which Nicolas Flamel had built, in which he died about 1417, and which, uninhabited ever since, was beginning to fall into decay, so much had the Hermetics and Alchemists from all the ends of the world worn away its walls by merely engraving their names upon them. Some of the neighbours even declared how, through a hole in the wall, they had seen the Archdeacon digging and turning over the earth in those two cellars, of which the door-jambs had been scrawled over with innumerable verses and hieroglyphics by Nicolas Flamel himself. It was supposed that Flamel had buried here the philosophers stone; and for two centuries the Alchemists, from Magistri to Pre Pacifique, never ceased to burrow in that ground, till at last the house, so cruelly ransacked and undermined, crumbled into dust under their feet. -Again, it is true that the Archdeacon was seized with a remarkable passion for the symbolical portal of Notre-Dame, that page of incantation written in stone by Bishop Guillaume of Paris, who is without doubt among the damned for having attached so infernal a frontispiece to the sacred poem eternally chanted by the rest of the edifice. The Archdeacon Claude was also credited with having solved the mystery of the colossal Saint-Christopher, and of that tall, enigmatical statue which stood then at the entrance of the Parvis of the Cathedral, and derisively styled by the people Monsieur le Grisold curmudgeon. But what nobody could fail to observe, were the interminable hours he would sometimes spend, seated on the parapet of the Parvis, lost in contemplation of the statues; now looking fixedly at the Foolish Virgins with their overturned lamps, now at the Wise Virgins with their lamps upright; at other times calculating the angle of vision of that raven perched on the left side of the central door and peering at a mysterious point inside the church, where most certainly the philosophers stone is hidden, if it is not in Nicolas Flamels cellar. -It was a singular destiny, we may remark in passing, for the Cathedral of Notre-Dame to be thus beloved in different degrees and with so much devotion by two creatures so utterly dissimilar as Claude Frollo and Quasimodo; loved by the one rudimentary, instinctive, savagefor its beauty, its lofty stature, the harmonies that flowed from its magnificent ensemble; loved by the othera being of cultured and perfervid imaginationfor its significance, its mystical meaning, the symbolic language lurking under the sculptures of its fa?ade, like the first manuscript under the second in a palimpsestin a word, for the enigma it eternally propounded to the intelligence. -Furthermore, it is certain that in one of the towers which overlooks the Grve, close by the cage of the bells, the Archdeacon had fitted up for himself a little cell of great secrecy, into which no one ever enterednot even the Bishop, without his leave. This cell had been constructed long ago, almost at the summit of the tower among the crows nests, by Bishop Hugh of Besan?on,5 who had played the necromancer there in his time. What this cell contained nobody knew; but on many a night from the shore of the terrain, from which a little round window at the back of the tower was visible, an unaccountable, intermittent red glow might be seen, coming and going at regular intervals, as if in response to the blowing of a pair of bellows, and as if it proceeded rather from a flame than a light. In the darkness, and at that height, the effect was very singular, and the old wives would say, Theres the Archdeacon blowing his bellows again! Hell-fire is blazing up there! -After all, these were no great proofs of sorcery; but still there was sufficient smoke to warrant the supposition of flame, and the Archdeacon therefore stood in decidedly bad odour. And yet we are bound to say that the occult sciences, that necromancy, magiceven of the whitest and most innocenthad no more virulent foe, no more merciless denouncer before the Holy Office of Notre-Dame than himself. Whether this abhorrence was sincere, or merely the trick of the pickpocket who cries Stop thief! it did not prevent the learned heads of the Chapter regarding him as a soul adventuring into the very fore-court of hell, lost among the holes and underground workings of the Cabala, groping in the baleful gloom of occult science. The people, of course, were not to be hood-winked for a momentany one with a grain of sense could see that Quasimodo was a demon, and Claude Frollo a sorcerer; and it was patent that the bell-ringer was bound to the Archdeacon for a certain time, after which he would carry off his masters soul in guise of payment. Consequently, in spite of the excessive austerity of his life, the Archdeacon was in bad repute with all pious people, and there was no devout nose, however inexperienced, that did not smell out the wizard in him. -Yet, if with advancing years deep fissures had opened in his mind, in his heart they were no less deep. So, at least, they had reason to think who narrowly scanned that face in which the soul shone forth as through a murky cloud. Else why that bald and furrowed brow, that constantly bowed head, those sighs that forever rent his breast? What secret thought sent that bitter smile to his lips at the selfsame moment that his frowning brows approached each other like two bulls about to fight? Why were his remaining hairs already gray? Whence came that inward fire that blazed at times in his eyes, till they looked like holes pierced in the wall of a furnace? -These symptoms of violent moral preoccupation had developed to an extraordinary degree of intensity at the period of our narrative. More than once had a chorister boy fled in terror when coming upon him suddenly in the Cathedral, so strange and piercing was his gaze. More than once, at the hour of service, had the occupant of the next stall in the choir heard him interspersing the plain song, ad omnem tonum, with unintelligible parentheses. More than once had the laundress of the terrain, whose duty it was to wash the Chapter, noticed with alarm the marks of finger-nails and clinched hands in the surplice of Monsieur the Archdeacon of Josas. -However, he grew doubly austere, and his life had never been more exemplary. By inclination, as well as by calling, he had always kept severely aloof from women; now he seemed to hate them more virulently than ever. The mere rustle of a silken kirtle was sufficient to make him bring his cowl down over his eyes. So jealous were his reserve and his austerity on this point, that when the Kings daughter, the Lady of Beaujeu, came in December, 1481, to visit the cloister of Notre-Dame, he earnestly opposed her admittance, reminding the Bishop of the statute in the Black Book, dated Saint-Bartholomews Eve, 1334, forbidding access to the cloister to every woman whatsoever, young or old, mistress or serving-maid. Upon which the Bishop had been constrained to quote the ordinance of the legate Odo, which makes exception in favour of certain ladies of high degree, who might not be turned away without offencealiqu? magnates mulieres, qu? sine scandale vitari non possunt. But the Archdeacon persisted in his protest, objecting that the legates ordinance, dating from as far back as 1207, was anterior to the Black Book by a hundred and twenty-seven years, and thus practically abrogated by it, and he refused to appear before the princess. -It was, moreover, noticed that, for some time past, his horror of gipsy-women and all Zingari in general had remarkably increased. He had solicited from the Bishop an edict expressly forbidding gipsies to dance or play the tambourine within the Parvis of the Cathedral; and simultaneously he was rummaging among the musty archives of the Holy Office, in order to collect all the cases of necromancers and sorcerers condemned to the flames or the halter for complicity in witchcraft with sows, he, or she-goats. -______________________ -1 Title attaching to a certain class of the priesthood, equivalent to The Reverend. -2 A brawl, the immediate result of too liberal potations. -3 A street of ill-fame. -4 Where the world comes to an end. -5 Hugo II de Bisuncio, 1326-1332.Authors Note. -Chapter 6 - Unpopularity -The Archdeacon and the bell-ringer found, as we have said before, but little favour with the people, great or small, in the purlieus of the Cathedral. If Claude and Quasimodo went abroad, as occasionally happened, and they were seen in companythe servant following his mastertraversing the chilly, narrow, and gloomy streets in the vicinity of Notre-Dame, many an abusive word, many a mocking laugh or opprobrious gibe would harass them on their passage unless Claude Frollothough this was rarewalked with head erect and haughty bearing, offering a stern and well-nigh imperial front to the startled gaze of his assailants. -The couple shared in the neighbourhood the fate of those poets of whom Rgnier says: -Toutes sortes de gens vont aprs les potes, -Comme aprs les hiboux vont criant les fauvettes.1 -Now some ill-conditioned monkey would risk his skin and bones for the ineffable pleasure of sticking a pin in Quasimodos hump, or some pretty wench, with more freedom and impudence than was seemly, would brush the priests black robe, thrusting her face into his, while she sang the naughty song beginning: -Niche, niche, le diable est pris!2 -Anon, a group of squalid old women, crouching in the shade on the steps of a porch, would abuse the Archdeacon and the bell-ringer roundly as they passed, or hurl after them with curses the flattering remark: There goes one whose soul is like the other ones body! Or, another time, it would be a band of scholars playing at marbles or hopscotch who would rise in a body and salute them in classical manner, with some Latin greeting such as Eia! Eia! Claudius cum claudo!3 -But, as a rule, these amenities passed unheeded by either the priest or the bell-ringer. Quasimodo was too deaf, and Claude too immersed in thought to hear them. -______________________ -1 -All sorts of people run after the poets, -As after the owls fly screaming the linnets. -2 Hide, hide, the devil is caught! -3 Ho! ho! Claude with the cripple! -BOOK V -Chapter 1 - The Abbot of Saint-Martins -The fame of Dom Claude Frollo had spread abroad. To it, just about the time of his refusal to encounter the Lady of Beaujeu, he owed a visit which remained long in his memory. -It happened one evening. Claude had just retired after the evening office to his canonical cell in the cloister of Notre-Dame. Beyond a few glass phials pushed away into a corner and containing some powder which looked suspiciously like an explosive, the cell had nothing noteworthy or mysterious about it. Here and there were some inscriptions on the walls, but they consisted purely of learned axioms or pious extracts from worthy authors. The Archdeacon had just seated himself at a huge oak chest covered with manuscripts, and lighted by a three-armed brass lamp. He leaned his elbow on an open tome: Honorius of Autuns De pr?destinatione et libero arbitrio, 2 while he musingly turned over the leaves of a printed folio he had just brought over, the sole production of the printing-press which stood in his cell. His reverie was broken by a knock at the door. -Whos there? called the scholar in the friendly tone of a famished dog disturbed over a bone. -A friendJacques Coictier, answered a voice outside. -He rose and opened the door. -It was, in fact, the Kings physician, a man of some fifty years, the hardness of whose expression was somewhat mitigated by a look of great cunning. He was accompanied by another man. Both wore long, slate-gray, squirrel-lined robes, fastened from top to bottom and belted round the middle, and caps of the same stuff and colour. Their hands disappeared in their sleeves, their feet under their robes, and their eyes under their caps. -God save me, messire! said the Archdeacon, as he admitted them; I was far from expecting so flattering a visit at this late hour. And while he spoke thus courteously, he glanced suspiciously and shrewdly from the physician to his companion. -It is never too late to pay a visit to so eminent a scholar as Dom Claude Frollo of Tirechappe, replied Doctor Coictier, whose Burgundian accent let his sentences trail along with all the majestic effect of a long-trained robe. -The physician and the Archdeacon then embarked upon one of those congratulatory prologues with which, at that period, it was customary to usher in every conversation between scholars, which did not prevent them most cordially detesting one another. For the rest, it is just the same to-day; the mouth of every scholar who compliments another is a vessel full of honeyed gall. -The felicitations addressed by Claude to Jacques Coictier alluded chiefly to the numerous material advantages the worthy physician had succeeded in extracting, in the course of his much-envied career, from each illness of the Kinga surer and more profitable kind of alchemy than the pursuit of the philosophers stone. -Truly, Doctor Coictier, I was greatly rejoiced to learn of the promotion of your nephew, my reverend Superior, Pierre Vers, to a bishopric. He is made Bishop of Amiens, is he not? -Yes, Monsieur the Archdeacon, it is a gracious and merciful gift of the Lord. -Let me tell you you made a brave show on Christmas-day at the head of your company of the Chamber of Accountants, Monsieur the President. -Vice-President, Dom Claude. Alas! nothing more. -How fares it with your superb mansion in the Rue Saint-Andry des Arcs? It is in very truth a Louvre! And I am much taken by the apricot-tree sculptured on the door, with the pleasant play of words inscribed beneath it, A LAbri-Cotier. -Well, well, Ma?tre Claude, all this masons work costs me dearly. In the same measure as my house rises higher, my funds sink lower. -Oho! Have you not your revenues from the jail, and the provostship of the Palais de Justice, and the rents from all the houses, workshops, booths, and market-stalls within the circuit of Paris? That is surely an excellent milch cow. -My castellany of Poissy has not brought me in a sou this year. -But your toll dues at Triel, Saint-James, and Saint-Germain-en-Layethey are always profitable? -Six times twenty livres only, and not even Paris money at that. -But you have your appointment as Councillor to the Kingthat means a fixed salary surely? -Yes, Colleague Claude, but that cursed Manor of Poligny, they make such a coil about, is not worth more to me than sixty gold crownstaking one year with another. -The compliments which Dom Claude thus addressed to Jacques Coictier were uttered in that tone of veiled, bitter, sardonic raillery, with that grievous, yet cruel, smile of a superior and unfortunate man, who seeks a moments distraction in playing on the gross vanity of the vulgarly prosperous man. The other was quite unconscious of it. -By my soul! said Claude at last, pressing his hand, I rejoice to see you in such excellent health. -Thank you, Ma?tre Claude. -Speaking of health, cried Dom Claude, how is your royal patient? -He does not pay his doctor sufficiently well, said the physician with a side glance at his companion. -Do you really think that, friend Coictier? said the stranger. -These words, uttered in a tone of surprise and reproach, recalled the Archdeacons attention to the strangers presence, though, to tell the truth, he had never, from the moment he crossed the threshold, quite turned away from this unknown guest. Indeed, it required the thousand reasons Claude had for humouring the all-powerful physician of Louis XI to make him consent to receive him thus accompanied. Therefore, his expression was none of the friendliest when Jacques Coictier said to him: -By-the-bye, Dom Claude, I have brought a colleague, who was most desirous of seeing one of whom he has heard so much. -Monsieur is a scholar? asked the Archdeacon, fixing Coictiers companion with a penetrating eye. But from under the brows of the stranger he met a glance not less keen or less suspicious than his own. -He was, so far as one could judge by the feeble rays of the lamp, a man of about sixty, of middle height, and apparently ailing and broken. His face, although the features were sufficiently commonplace, had something commanding and severe; his eye glittered under the deep arch of his brow like a beacon-light far down a cavern; and under the cap, pulled down almost to his nose, one divined instinctively the broad forehead of a genius. -He took upon himself to answer the archdeacons inquiry. -Reverend sir, said he in grave tones, your fame has reached me, and I was desirous of consulting you. I am but a poor gentleman from the provinces who takes the shoes off his feet before entering the presence of the learned. I must acquaint you with my name: they call me Compre2 Tourangeau. -Singular name for a gentleman, thought the Archdeacon. Nevertheless, he felt himself in the presence of something powerful and commanding. The instinct of his high intelligence led him to suspect one no less high beneath the fur-trimmed cap of Compre Tourangeau; and as he scrutinized that quiet figure, the sneering smile that twitched round the corners of his morose mouth as he talked to Coictier faded slowly away, like the sunset glow from an evening sky. -He had seated himself again, gloomy and silent, in his great arm-chair, his elbow had resumed its accustomed place on the table, his head leaning on his hand. -After a few moments of deep reflection, he signed to his two visitors to be seated, and then addressed himself to Compre Tourangeau. -You came to consult me, sir, and on what subject? -Your Reverence, answered Tourangeau, I am sick, very sick. Rumour says you are a great ?sculapius, and I am come to ask your advice as to a remedy. -A remedy! exclaimed the Archdeacon, shaking his head. He seemed to consider for a moment, and then resumed: Compre Tourangeausince that is your nameturn your head. You will find my answer written on the wall. -Tourangeau did as he was bid, and read the following inscription on the wall, above his head: Medicine is the daughter of dreams.Iamblichus. -Doctor Jacques Coictier had listened to his companions question with a vexation which Dom Claudes answer only served to increase. He now leaned over to Tourangeau and whispered, too low for the Archdeacons ear: Did I not warn you that he was a crack-brained fool? You were set upon seeing him. -But it might very well be that he is right in his opinion, this madman, Doctor Jacques, returned his friend in the same tone, and with a bitter smile. -Just as you please, answered Coictier dryly. You are very quick in your decision, Dom Claude, and Hippocrates apparently presents no more difficulties to you than a nut to a monkey. Medicine a dream! I doubt if the apothecaries and doctors, were they here, could refrain from stoning you. So you deny the influence of philters on the blood, of unguents on the flesh? You deny the existence of that eternal pharmacy of flowers and metals which we call the World, created expressly for the benefit of that eternal invalid we call Man! -I deny the existence, answered Dom Claude coldly, neither of the pharmacy nor the invalid. I deny that of the physician. -Then, I presume it is not true, Coictier went on with rising heat, that gout is an internal eruption; that a shot-wound may be healed by the outward application of a roasted mouse; that young blood, injected in suitable quantities, will restore youth to aged veins; it is not true that two and two make four, and that emprosthotonos follows upon opisthotonos? -There are certain matters about which I think in a certain way, the Archdeacon replied unmoved. -Coictier flushed an angry red. -Come, come, my good Coictier, do not let us get angry, said Compre Tourangeau, the reverend Archdeacon is our host. -Coictier calmed down, but growled to himself: Hes a madman, for all that. -Pasque Dieu! resumed Tourangeau, after a short silence; you put me in a very embarrassing position, Ma?tre Claude. I looked to obtaining two opinions from you, one as to my health, the other as to my star. -Monsieur, returned the Archdeacon, if that is your idea, you would have done better not to waste your health in mounting my stairs. I do not believe in medicine, and I do not believe in astrology. -Is that so? exclaimed the good man in surprise. -Coictier burst into a forced laugh. -You must admit now that hes mad, he said in low tones to Tourangeau; he does not believe in astrology. -How can any one possibly believe, continued Dom Claude, that every ray of a star is a thread attached to a mans head? -And what do you believe in then? cried Tourangeau. -The Archdeacon hesitated for a moment, then, with a sombre smile which seemed to give the lie to his words, he answered, Credo in Deum. -Dominum nostrum, added Tourangeau, making the sign of the cross. -Amen, said Coictier. -Reverend sir, resumed Tourangeau, I am charmed to my soul to find you so firm in the faith. But, erudite scholar that you are, have you reached the point of no longer believing in science? -No! cried the Archdeacon, grasping Tourangeaus arm, while a gleam of enthusiasm flashed in his sunken eye; no, I do not deny science. I have not crawled so long on my belly with my nails dug in the earth through all the innumerable windings of that dark mine, without perceiving in the far distanceat the end of the dim passagea light, a flame, a something; the reflection, no doubt, from that dazzling central laboratory in which the patient and the wise have come upon God. -And finally, interrupted Tourangeau, what do you hold for true and certain? -Alchemy! -Coictier exclaimed aloud, Pardieu, Dom Claude, there is doubtless much truth in alchemy, but why blaspheme against medicine and astrology? -Null is your science of man, your science of the heavens null, said the Archdeacon imperiously. -But thats dealing hardly with Epidaurus and Chaldea, returned the physician with a sneering laugh. -Listen, Messire Jacques. I speak in all good faith. I am not physician to the King, and his Majesty did not give me a Labyrinth in which to observe the constellations. Nay, be not angry, but listen to what I say: what truths have you extracted from the studyI will not say of medicine, which is too foolish a mattebut from astrology? Explain to me the virtues of the vertical boustrophedon,3 or the treasures contained in the numeral ziruph, and in those of the numeral zephirod. -Will you deny, said Coictier, the sympathetic influence of the clavicula, and that it is the key to all cabalistic science? -Errors, Messire Jacques! None of your formulas have anything definite to show, whereas alchemy has its actual discoveries. Can you contest such results as these, for instanceice, buried underground for two thousand years, is converted into rock crystal; lead is the progenitor of all metals (for gold is not a metal, gold is light); lead requires but four periods of two hundred years each to pass successively from the condition of lead to that of red arsenic, from red arsenic to tin, from tin to silver. Are these facts, or are they not? But to believe in the clavicula, in the mystic significance of the junction of two lines, in the stars, is as ridiculous as to believe, like the inhabitants of Cathay, that the oriole changes into a mole, and grains of wheat into carp-like fish. -I have studied hermetics, cried Coictier, and I affirm -The impetuous Archdeacon would not let him finish. And II have studied medicine, astrology, and hermetics. Here alone is truth (and as he spoke he took up one of those phials of glass of which mention has been made), here alone is light! Hippocratesa dream; Uraniaa dream; Hermesa phantasm. Gold is the sun; to make gold is to be God. There is the one and only science. I have sounded medicine and astrology to their depthsnull, I tell younull and void! The human bodydarkness! the starsdarkness! -He sank into his chair with a compelling and inspired gesture. Tourangeau observed him in silence; Coictier forced a disdainful laugh, shrugging his shoulders imperceptibly while he repeated under his breath, Madman. -Well, said Tourangeau suddenly, and the transcendental resulthave you achieved it? Have you succeeded in making gold? -If I had, answered the Archdeacon, dropping his words slowly like a man in a reverie, the name of the King of France would be Claude and not Louis. -Tourangeau bent his brow. -Pah, what am I saying? resumed Dom Claude with a disdainful smile. What would the throne of France be to me when I could reconstruct the Empire of the East? -Well done! exclaimed Tourangeau. -Poor ass! murmured Coictier. -No, the Archdeacon went on, as if in answer to his own thoughts, I am still crawling, still bruising my face and my knees against the stones of the subterranean path. Fitful glimpses I catch, but nothing clear. I cannot readI am but conning the alphabet. -And when you have learned to read, will you be able to make gold? -Who doubts it? answered the Archdeacon. -In that caseOur Lady knows I am in dire need of moneyI would gladly learn to read in your books. Tell me, reverend master, is not your science inimical and displeasing to Our Lady, think you? -To this question of Tourangeaus Dom Claude contented himself by making answer with quiet dignity, Whose priest am I? -True, true, master. Well, then, will it please you to initiate me? Let me learn to spell with you? -Claude assumed the majestic and saceidotal attitude of a Samuel. -Old man, it would require more years than yet remain to you to undertake this journey across the world of mystery. Your head is very gray! One emerges from the cave with white hair, but one must enter it with black. Science knows very well how to furrow and wither up the face of man without assistance; she has no need that age should bring to her faces that are already wrinkled. Nevertheless, if you are possessed by the desire to put yourself under tutelage at your age, and to decipher the awful alphabet of Wisdom, well and good, come to me, I will do what I can. I will not bid you, poor graybeard, go visit the sepulchral chambers of the Pyramids, of which the ancient Herodotus speaks, nor the brick tower of Babylon, nor the vast marble sanctuary of the Indian Temple of Eklinga. I have not seen, any more than you have, the Chaldean walls built in accordance with the sacred formula of Sikra, nor the Temple of Solomon which was destroyed, nor the stone doors of the sepulchres of the Kings of Israel which are broken in pieces. Such fragments of the Book of Hermes as we have here will suffice us. I will explain to you the statue of Saint-Christopher, the symbol of the Sower, and that of the two angels in the door of the Sainte-Chapelle, of whom one has his hand in a stone vessel, and the other in a cloud. -Here Jacques Coictier, who had been quite confounded by the Archdeacons tempestuous flow of eloquence, recovered his composure and struck in with the triumphant tone of one scholar setting another right: -Erras, amice Claudithere you are in error. The symbol is not the numeral. You mistake Orpheus for Hermes. -It is you who are in error, returned the Archdeacon with dignity; D?dalus is the foundation; Orpheus is the wall; Hermes is the edificethe whole structure. Come whenever it please you, he continued, turning to Tourangeau. I will show you the particles of gold left in the bottom of Nicolas Flamels crucible which you can compare with the gold of Guillaume de Paris. I will instruct you in the secret virtues of the Greek word peristera. But before all things, you shall read, one after another, the letters of the marble alphabet, the pages of the granite book. We will go from the doorway of Bishop Guillaume and of Saint-Jean le Rond to the Sainte-Chapelle, then to the house of Nicolas Flamel in the Rue Marivault, to his tomb in the cemetery of the Holy Innocents, to his two hospices in the Rue de Montmorency. You shall read the hieroglyphics with which the four great iron bars in the porch of the Hospice of Saint-Gervais are covered. Together we will spell out the fa?ades of Saint-C?me, of Sainte-Genevive-des-Ardents, Saint-Martin, Saint-Jacques-de-la-Boucherie -For some time past, Tourangeau, with all his intelligence, appeared unable to follow Dom Claude. He broke in now: -Pasque Dieu! but what are these books of yours? -Here is one, replied the Archdeacon; and opening the window of his cell, he pointed to the mighty Cathedral of Notre-Dame, the black silhouette of its two towers, its stone sides, and its huge roof sharply outlined against the starry sky, and looking like an enormous two-headed sphinx crouching in the midst of the city. -For some moments the Archdeacon contemplated the gigantic edifice in silence; then, sighing deeply, he pointed with his right hand to the printed book lying open on his table, and with his left to Notre-Dame, and casting a mournful glance from the book to the church: -Alas! he said. This will destroy that. -Coictier, who had bent eagerly over the book, could not repress an exclamation of disappointment. H! but what is there so alarming in this? Glossa in Epistolas Pauli. Norimberg?, Antonius Koburger 1474. That is not new. It is a book of Petrus Lombardus, the Magister Sententiarum. Do you mean because it is printed? -You have said it, returned Claude, who stood apparently absorbed in profound meditation, with his finger on the folio which had issued from the famous printing-press of Nuremberg. Presently he uttered these dark words: Woe! woe! the small brings down the great; a tooth triumphs over a whole mass! The Nile rat destroys the crocodile, the sword-fish destroys the whale, the book will destroy the edifice! -The curfew of the cloister rang at this moment as Doctor Jacques whispered to his companion his everlasting refrain of He is mad! To which the companion replied this time, I believe he is. -It was the hour after which no stranger might remain in the cloister. The two visitors prepared to retire. -Ma?tre, said Compre Tourangeau, as he took leave of the Archdeacon, I have a great regard for scholars and great spirits, and I hold you in peculiar esteem. Come tomorrow to the Palais des Tournelles, and ask for the Abbot of Saint-Martin of Tours. -The Archdeacon returned to his cell dumfounded, comprehending at last who the personage calling himself Compre Tourangeau really was: for he called to mind this passage in the Charter of Saint-Martin of Tours: Abbas, beati Martini, scilicet Rex Franci?, est canonicus de consuetudine et habet parvam pr?bendam quam habet sanctus Venantius, et debet sedere in sede thesaurii.4 -It is asserted that from that time onward the Archdeacon conferred frequently with Louis XI, whenever his Majesty came to Paris, and that the Kings regard for Dom Claude put Olivier le Daim and Jacques Coictier quite in the shade, the latter of whom, as was his custom, rated the King soundly in consequence. -______________________ -1 Of Predestination and Free-Will. -2 Goodman, gossip. -3 Writing from right to left and back again from left to right without breaking off the lines. -4 The Abbot of Saint-Martin, that is to say the King of France, is canon, according to custom, and has the small benefice which Saint-Venantis had, and shall sit in the seat of the treasurer. -Chapter 2 - This Will Destroy That -Our fair readers must forgive us if we halt a moment here and endeavour to unearth the idea hidden under the Archdeacons enigmatical words: -This will destroy That. The Book will destroy the Edifice. -To our mind, this thought has two aspects. In the first place it was a view pertaining to the priestit was the terror of the ecclesiastic before a new forceprinting. It was the servant of the dim sanctuary scared and dazzled by the light that streamed from Gutenbergs press. It was the pulpit and the manuscript, the spoken and the written word quailing before the printed wordsomething of the stupefaction of the sparrow at beholding the Heavenly Host spread their six million wings. It was the cry of the prophet who already hears the far-off roar and tumult of emancipated humanity; who, gazing into the future, sees intelligence sapping the foundations of faith, opinion dethroning belief, the world shaking off the yoke of Rome; the prognostication of the philosopher who sees human thought volatilized by the press, evaporating out of the theocratic receiver; the terror of the besieged soldier gazing at the steel battering-ram and saying to himself, The citadel must fall. It signified that one great power was to supplant another great power. It meant, The Printing-Press will destroy the Church. -But underlying this thoughtthe first and no doubt the less complex of the twothere was, in our opinion, a second, a more moderna corollary to the former idea, less on the surface and more likely to be contested; a view fully as philosophic, but pertaining no longer exclusively to the priest, but to the scholar and the artist likewise. It was a premonition that human thought, in changing its outward form, was also about to change its outward mode of expression; that the dominant idea of each generation would, in future, be embodied in a new material, a new fashion; that the book of stone, so solid and so enduring, was to give way to the book of paper, more solid and more enduring still. In this respect the vague formula of the Archdeacon had a second meaningthat one Art would dethrone another Art: Printing will destroy Architecture. -In effect, from the very beginning of things down to the fifteenth century of the Christian era inclusive, architecture is the great book of the human race, mans chief means of expressing the various stages of his development, whether physical or mental. -When the memory of the primitive races began to be surcharged, when the load of tradition carried about by the human family grew so heavy and disordered that the word, naked and fleeting, ran danger of being lost by the way, they transcribed it on the ground by the most visible, the most lasting, and at the same time most natural means. They enclosed each tradition in a monument. -The first monuments were simply squares of rock which had not been touched by iron, as says Moses. Architecture began like all writing. It was first an alphabet. A stone was planted upright and it was a letter, and each letter was a hieroglyph, and on every hieroglyph rested a group of ideas, like the capital on the column. Thus did the primitive races act at the same moment over the entire face of the globe. One finds the upright stone of the Celts in Asiatic Siberia and on the pampas of America. -Presently they constructed words. Stone was laid upon stone, these granite syllables were coupled together, the word essayed some combinations. The Celtic dolmen and cromlech, the Etruscan tumulus, the Hebrew galgal, are wordssome of them, the tumulus in particular, are proper names. Occasionally, when there were many stones and a vast expanse of ground, they wrote a sentence. The immense mass of stones at Karnac is already a complete formula. - of all they made books. Traditions had ended by bringing forth symbols, under which they disappeared like the trunk of a tree under its foliage. These symbols, in which all humanity believed, continued to grow and multiply, becoming more and more complex; the primitive monumentsthemselves scarcely expressing the original traditions, and, like them, simple, rough-hewn, and planted in the soilno longer sufficed to contain them: they overflowed at every point. Of necessity the symbol must expand into the edifice. Architecture followed the development of human thought; it became a giant with a thousand heads, a thousand arms, and caught and concentrated in one eternal, visible, tangible form all this floating symbolism. While D?dalus, who is strength, was measuring; while Orpheus, who is intelligence, was singingthe pillar, which is a letter; the arch, which is a syllable; the pyramid, which is a word, set in motion at once by a law of geometry and a law of poetry, began to group themselves together, to combine, to blend, to sink, to rise, stood side by side on the ground, piled themselves up into the sky, till, to the dictation of the prevailing idea of the epoch, they had written these marvellous books which are equally marvellous edifices: the Pagoda of Eklinga, the Pyramids of Egypt, and the Temple of Solomon. -The parent idea, the Word, was not only contained in the foundation of these edifices, but in their structure. Solomons Temple, for example, was not simply the cover of the sacred book, it was the sacred book itself. On each of its concentric enclosures the priest might read the Word translated and made manifest to the eye, might follow its transformations from sanctuary to sanctuary, till at last he could lay hold upon it in its final tabernacle, under its most concrete form, which yet was architecturethe Ark. Thus the Word was enclosed in the edifice, but its image was visible on its outer covering, like the human figure depicted on the coffin of a mummy. -Again, not only the structure of the edifice but its situation revealed the idea it embodied. According as the thought to be expressed was gracious or sombre, Greece crowned her mountains with temples harmonious to the eye; India disembowelled herself to hew out those massive subterranean pagodas which are supported by rows of gigantic granite elephants. -Thus, during the first six thousand years of the worldfrom the most immemorial temple of Hindustan to the Cathedral at Colognearchitecture has been the great manuscript of the human race. And this is true to such a degree, that not only every religious symbol, but every human thought, has its page and its memorial in that vast book. -Every civilization begins with theocracy and ends with democracy. -The reign of many masters succeeding the reign of one is written in architecture. Forand this point we must emphasizeit must not be supposed that it is only capable of building temples, of expressing only the sacerdotal myth and symbolism, of transcribing in hieroglyphics on its stone pages the mysterious Tables of the Law. Were this the case, thenseeing that in every human society there comes a moment when the sacred symbol is worn out, and is obliterated by the free thought, when the man breaks away from the priest, when the growth of philosophies and systems eats away the face of religionarchitecture would be unable to reproduce this new phase of the human mind: its leaves, written upon the right side, would be blank on the reverse; its work would be cut short; its book incomplete. But that is not the case. -Take, for example, the epoch of the Middle Ages, which is clearer to us because it is nearer. During its first period, while theocracy is organizing Europe, while the Vatican is collecting and gathering round it the elements of a new Rome, constructed out of the Rome which lay in fragments round the Capitol, while Christianity goes forth to search among the ruins of a former civilization, and out of its remains to build up a new hierarchic world of which sacerdotalism is the keystone, we hear it stirring faintly through the chaos; then gradually, from under the breath of Christianity, from under the hands of the barbarians, out of the rubble of dead architectures, Greek and Romanthere emerges that mysterious Romanesque architecture, sister of the theocratic buildings of Egypt and India, inalterable emblem of pure Catholicism, immutable hieroglyph of papal unity. The whole tendency of the time is written in this sombre Romanesque style. Everywhere it represents authority, unity, the imperturbable, the absolute, Gregory VII; always the priest, never the man: everywhere the caste, never the people. -Then come the Crusades, a great popular movement, and every popular movement, whatever its cause or its aim, has as its final precipitation the spirit of liberty. Innovations struggled forth to the light. At this point begins the stormy period of the Peasant wars, the revolts of the Burghers, the Leagues of the Princes. Authority totters, unity is split and branches off into two directions. Feudalism demands to divide the power with theocracy before the inevitable advent of the people, who, as ever, will take the lions shareQuia nominor leo. Hence we see feudalism thrusting up through theocracy, and the peoples power again through feudalism. The whole face of Europe is altered. Very good; the face of architecture alters with it. Like civilization, she has turned a page, and the new spirit of the times finds her prepared to write to his dictation. She has brought home with her from the crusades the pointed arch, as the nations have brought free thought. Henceforward, as Rome is gradually dismembered, so the Romanesque architecture dies out. The hieroglyphic deserts the Cathedral, and goes to assist heraldry in heightening the prestige of feudalism. The Cathedral itself, once so imbued with dogma, invaded now by the commonalty, by the spirit of freedom, escapes from the priest, and falls under the dominion of the artist. The artist fashions it after his own good pleasure. Farewell to mystery, to myth, to rule. Here fantasy and caprice are a law unto themselves. Provided the priest has his basilica and his altar, he has nothing further to say in the matter. The four walls belong to the artist. The stone book belongs no more to the priest, to religion, to Rome, but to imagination, to poetry, to the people. From thenceforward occur these rapid and innumerable transformations of an architecture only lasting three centuries, so striking after the six or seven centuries of stagnant immobility of the Romanesque style. Meanwhile, Art marches on with giant strides, and popular originality plays what was formerly the Bishops part. Each generation in passing inscribes its line in the book; it rubs out the ancient Roman hieroglyphics from the frontispiecehardly that one sees here and there some dogma glimmering faintly through the new symbol overlying it. The framework of religion is scarcely perceptible through this new drapery. One can scarcely grasp the extent of the license practised at that time by the architects, even on the churches. Such are the shamelessly intertwined groups of monks and nuns on the capitals of the Gallery of Chimney-Pieces in the Palais de Justice; the episode out of the history of Noah sculptured to the letter over the Cathedral door at Bourges; the bacchic monk, with asss ears and glass in hand, grinning in the face of a whole congregation, carved on a stone basin of the Abbey of Bocherville. For the thought written in stone there existed at that period a privilege perfectly comparable to the present liberty of the press. It was the liberty of architecture. -And the liberty went far. At times a door, a fa?ade, nay, even an entire church, presents a symbolical meaning wholly unconnected with worship, even inimical to the Church itself. In the thirteenth century, Guillaume of Paris, and in the fifteenth, Nicolas Flamel wrote such seditious pages. Saint-Jacques-de-la-Boucherie was a complete volume of opposition. -This was the only form, however, in which free thought was possible, and therefore found full expression only in those books called edifices. Under that form it might have looked on at its own burning at the hands of the common hangman had it been so imprudent as to venture into manuscript: the thought embodied in the church door would have assisted at the death agony of the thought expressed in the book. Therefore, having but this one outlet, it rushed towards it from all parts; and hence the countless mass of Cathedrals spread over all Europe, a number so prodigious that it seems incredible, even after verifying it with ones own eyes. All the material, all the intellectual forces of society, converged to that one pointarchitecture. In this way, under the pretext of building churches to the glory of God, the art developed to magnificent proportions. -In those days, he who was born a poet became an architect. All the genius scattered among the masses and crushed down on every side under feudalism, as under a testudo of brazen bucklers, finding no outlet but in architecture, escaped by way of that art, and its epics found voice in cathedrals. All other arts obeyed and put themselves at the service of the one. They were the artisans of the great work; the architect summed up in his own person, sculpture, which carved his fa?ade; painting, which dyed his windows in glowing colours; music, which set his bells in motion and breathed in his organ pipes. Even poor Poetryproperly so called, who still persisted in eking out a meagre existence in manuscriptwas obliged, if she was to be recognised at all, to enroll herself in the service of the edifice, either as hymn or prosody; the small part played, after all, by the tragedies of ?schylus in the sacerdotal festivals of Greece, and the Book of Genesis in the Temple of Solomon. -Thus, till Gutenbergs time, architecture is the chief, the universal form of writing; in this stone book, begun by the East, continued by Ancient Greece and Rome, the Middle Ages have written the last page. For the rest, this phenomenon of an architecture belonging to the people succeeding an architecture belonging to a caste, which we have observed in the Middle Ages, occurs in precisely analogous stages in human intelligence at other great epochs of history. Thusto sum up here in a few lines a law which would call for volumes to do it justicein the Far East, the cradle of primitive history, after Hindu architecture comes the Ph?nician, that fruitful mother of Arabian architecture; in antiquity, Egyptian architectureof which the Etruscan style and the Cyclopean monuments are but a varietyis succeeded by the Greek, of which the Roman is merely a prolongation burdened with the Carthaginian dome; in modern times, after Romanesque architecture comes the Gothic. And if we separate each of these three divisions, we shall find that the three elder sistersHindu, Egyptian, and Roman architecturestand for the same idea: namely, theocracy, caste, unity, dogma, God; and that the three younger sistersPh?nician, Greek, Gothicwhatever the diversity of expression inherent to their nature, have also the same significance: liberty, the people, humanity. -Call him Brahmin, Magi, or Pope, according as you speak of Hindu, Egyptian, or Roman buildings, it is always the priest, and nothing but the priest. Very different are the architectures of the people; they are more opulent and less saintly. In the Ph?nician you see the merchant, in the Greek the republican, in the Gothic the burgess. -The general characteristics of all theocratic architectures are immutability, horror of progress, strict adherence to traditional lines, the consecration of primitive types, the adaptation of every aspect of man and nature to the incomprehensible whims of symbolism. Dark and mysterious book, which only the initiated can decipher! Furthermore, every form, every deformity even, in them has a meaning which renders it inviolable. Never ask of Hindu, Egyptian, or Roman architecture to change its designs or perfect its sculpture. To it, improvement in any shape or form is an impiety. Here the rigidity of dogma seems spread over the stone like a second coating of petrifaction. -On the other hand, the main characteristics of the popular architectures are diversity, progress, originality, richness of design, perpetual change. They are already sufficiently detached from religion to take thought for their beauty, to tend it, to alter and improve without ceasing their garniture of statues and arabesques. They go with their times. They have something human in them which they constantly infuse into the divine symbols in which they continue to express themselves. Here you get edifices accessible to every spirit, every intelligence, every imagination; symbolic still, but as easily understood as the signs of Nature. Between this style of architecture and the theocratic there is the same difference as between the sacred and the vulgar tongue, between hieroglyphics and art, between Solomon and Phidias. -In fact, if we sum up what we have just roughly pointed outdisregarding a thousand details of proof and also exceptions to the ruleit comes briefly to this: that down to the fifteenth century, architecture was the chief recorder of the human race; that during that space no single thought that went beyond the absolutely fundamental, but was embodied in some edifice; that every popular idea, like every religious law, has had its monuments; finally, that the human race has never conceived an important thought that it has not written down in stone. And why? Because every thought, whether religious or philosophic, is anxious to be perpetuated; because the idea which has stirred one generation longs to stir others, and to leave some lasting trace. But how precarious is the immortality of the manuscript! How far more solid, enduring, and resisting a book is the edifice! To destroy the written word there is need only of a torch and a Turk. To destroy the constructed word there is need of a social revolution, a terrestrial upheaval. The barbarians swept over the Coliseum; the deluge, perhaps, over the Pyramids. -In the fifteenth century all is changed. -Human thought discovers a means of perpetuating itself, not only more durable and more resisting than architecture, but also simpler and more easy of achievement. Architecture is dethroned, the stone letters of Orpheus must give way to Gutenbergs letters of lead. -The Book will destroy the Edifice. -The invention of printing is the greatest event of history. It is the parent revolution; it is a fundamental change in mankinds mode of expression; it is human thought putting off one shape to don another; it is the complete and definite sloughing of the skin of that serpent who, since the days of Adam, has symbolized intelligence. -Under the form of printing, thought is more imperishable than ever; it is volatile, intangible, indestructible; it mingles with the very air. In the reign of architecture it became a mountain, and took forceful possession of an era, of a country. Now it is transformed into a flock of birds, scattering to the four winds and filling the whole air and space. -We repeat: who does not admit that in this form thought is infinitely more indelible? The stone has become inspired with life. Durability has been exchanged for immortality. One can demolish substance, but how extirpate ubiquity? Let a deluge comethe birds will still be flying above the waters long after the mountain has sunk from view; and let but a single ark float upon the face of the cataclysm, and they will seek safety upon it and there await the subsiding of the waters; and the new world rising out of this chaos will behold when it wakes, hovering over it, winged and unharmed, the thought of the world that has gone down. -And when one notes that this mode of expression is not only the most preservative, but also the simplest, the most convenient, the most practicable for all; when one considers that it is not hampered by a great weight of tools and clumsy appurtenances; when one compares the thought, forced, in order to translate itself into an edifice, to call to its assistance four or five other arts and tons of gold, to collect a mountain of stones, a forest of wood, a nation of workmenwhen one compares this with the thought that only asks for a little paper, a little ink, and a pen in order to become a book, is it any wonder that human intelligence deserted architecture for printing? -Then observe too, how, after the discovery of printing, architecture gradually becomes dry, withered, naked; how the water visibly sinks, the sap ceases to rise, the thought of the times and of the peoples desert it. This creeping paralysis is hardly perceptible in the fifteenth century, the press is too feeble as yet, and what it does abstract from all-powerful architecture is but the superfluity of its strength. But by the sixteenth century the malady is pronounced. Already architecture is no longer the essential expression of social life; it assumes miserable classic air; from Gallican, European, indigenous, it becomes bastard Greek and Roman, from the genuine and the modern it becomes pseudo-antique. This decadence we call the Renaissancea magnificent decadence nevertheless, for the ancient Gothic genius, that sun now sinking behind the gigantic printing-press of Mayence, sheds for a little while its last rays over this hybrid mass of Romanesque arches and Corinthian colonnades. -And it is this sunset that we take for the dawn of a new day. -However, from the moment that architecture is nothing more than an art like any otheris no longer the sum total of art, the sovereign, the tyrantit is powerless to monopolize the services of the others, who accordingly emancipate themselves, throw off the yoke of the architect and go their separate ways. Each art gains by this divorce. Thus isolated, each waxes great. Stone-masonry becomes sculpture; pious illumination, painting; the restricted chant blooms out into concerted music. It is like an empire falling asunder on the death of its Alexander, and each province becoming an independent kingdom. -For here begins the period of Raphael, Michael Angelo, Jean Goujon, Palestrinathose luminaries of the dazzling firmament of the sixteenth century. -And with the arts, thought, too, breaks its bonds on all sides. The free-thinkers of the Middle Ages had already inflicted deep wounds on Catholicism. The sixteenth century rends religious unity in pieces. Before printing, the Reformation would merely have been a schism: printing made it a revolution. Take away the press, and heresy is paralyzed. Look on it as fatal or providential, Gutenberg is the fore-runner of Luther. -But when the sun of the Middle Ages has wholly set, when the radiance of Gothic genius has faded forever from the horizon of art, architecture, too, grows slowly pale, wan and lifeless. The printed book, that gnawing worm, sucks the life-blood from her and devours her. She droops, she withers, she wastes away before the eye. She becomes mean and poor, of no account, conveying nothing to the mindnot even the memory of the art of other days. Reduced to her own exertions, deserted by the other arts because human thought has left her in the lurch, she has to employ the artisan in default of the artist. Plain glass replaces the glowing church window, the stone-mason the sculptor; farewell to vital force, to originality, life or intelligence; as a lamentabie beggar of the studios she drags herself from copy to copy. Michael Angelo, doubtless sensible of her approaching end, made one last despairing effort in her aid. That Titan of the world of art piled the Pantheon on the Parthenon and so made Saint-Peters of Romea gigantic work that deserved to remain unique, the last orginality of architecture, the signature of a mighty artist at the bottom of the colossal register of stone thus closed. But Michael Angelo once dead, what does this wretched architecture do, which only survives as a spectre, as a shade? She takes Saint-Peters and copies, parodies it. It becomes a mania with her, a thing to weep at: in the seventeenth century the Val-de-Grace, in the eighteenth, Sainte-Genevive. Every country has its Saint-Peters. London has hers, St. Petersburg hers, Paris even two or threea legacy of triviality, the last drivellings of a grand but decrepit art, fallen into second childhood before its final dissolution. -If, instead of the characteristic monuments like those of which we have spoken, we examine the general aspect of the art from the sixteenth to the eighteenth century, we shall find everywhere the same evidences of decrepitude and decay. From the time of Francis II the form of the edifice lets the geometrical outline show through more and more, like the bony framework through the skin of an emaciated body. The generous curves of art give place to the cold and inexorable lines of geometry. An edifice is no longer an edifice, it is a polyhedron. Architecture, however, is at infinite pains to cover her nakedness, and hence the Greek pediment set in the Roman pediment and vice versa. It is always the Pantheon on the Parthenon, Saint-Peters at Rome. Such are the brick houses with stone corners of the time of Henri IV, the Place Royale, the Place Dauphine. Such are the churches of Louis XIII, heavy, squat, compressed, with a dome like a hump. Thus, too, is the Mazarin architecture, the poor Italian pasticcio of the Quatre-Nations, the palaces of Louis XIV, mere court barracks, endless, frigid, wearisome; and finally, the style of Louis XV with its chicory-leaf and vermicelli ornaments, and all the warts and growths disfiguring that aged, toothless, demoralized coquette. From Francis II to Louis XV the malady progressed in geometrical ratio. The art is reduced to skin and bone, her life ebbs miserably away. -Meanwhile, what of the art of printing? All the vital force taken from architecture streams to her. As architecture sinks, so printing rises and expands. The store of strength spent hitherto by human thought on edifices is now bestowed on books; till, by the sixteenth century, the press, grown now to the level of her shrunken rival, wrestles with her and prevails. In the seventeenth century she is already so absolute, so victorious, so firmly established on her throne, that she can afford to offer to the world the spectacle of a great literary era. In the eighteenth century, after long idleness at the Court of Louis XIV, she takes up again the ancient sword of Luther, thrusts it into Voltaires hand, and runs full tilt at that antiquated Europe whom she has already robbed of all architectural expression. Thus, as the eighteenth century ends she has accomplished her work of destruction; with the nineteenth century she begins to construct. -Now which of these two arts, we ask, represents in truth the course of human thought during three centuries; which of the two transmits, expresses, not only its fleeting literary and scholastic fashion, but its vast, profound, all-embracing tendencies? Which of the two has fitted itself like a skin, without a crease or gap, over that thousand-footed, never-resting monster, the human race? Architecture or Printing? -Printing. Let no one mistake: architecture is deaddead beyond recall, killed by the printed book, killed because it is less durable, killed because it is more costly. Every Cathedral represents a million. Imagine now the sums necessary for the rewriting of that architectural tome; for those countless edifices to spread once more over the land; to return to the days when their abundance was such that from the testimony of an eye-witness you would have thought that the world had cast off its old raiment and clad itself anew in a white raiment of churches. Erat enim ut si mundus, ipse excutiendo semet, rejecta vetustate, candidam ecclesiarum vestem indueret. (Glaber Rudolphus.) -A book takes so little time in the making, costs so little, and can reach so far. What wonder that human thought should choose that path? Though this is not to say that architecture will not, from time to time, put forth some splendid monument, some isolated master-piece. There is no reason why, under the reign of printing, we should not, some time or other, have an obelisk constructed, say, by an entire army out of melted cannon, as, under the reign of architecture, we had the Iliads, the Romants, the Mahabahratas, and the Nibelungen, built by whole nations with the welded fragments of a thousand epics. The great good fortune of possessing an architect of genius may befall the twentieth century, as Dante came to the thirteenth. But architecture will never again be the social, the collective, the dominant art. The great epic, the great monument, the great masterpiece of mankind will never again be built; it will be printed. -And even if, by some fortuitous accident, architecture should revive, she will never again be mistress. She will have to submit to those laws which she once imposed upon literature. The respective positions of the two arts will be reversed. Certainly, under the reign of architecture, the poemsrare, trueresemble the monuments of the time. The Indian Vyasa is strange, variegated, unfathomable, like the native pagoda. In Egypt the poetry shares the grand and tranquil lines of the edifices; in ancient Greece it has their beauty, serenity, and calm; in Christian Europe, the majesty of the Church, the simplicity of the people, the rich and luxuriant vegetation of a period of rebirth. The Bible corresponds to the Pyramids, the Iliad to the Parthenon, Homer to Phidias. Dante in the thirteenth century is the last Romanesque church; Shakespeare in the sixteenth, the last Gothic minster. -Thus, to put it shortly, mankind has two books, two registers, two testaments: Architecture and Printing; the Bible of stone and the Bible of paper. Doubtless, in contemplating these two Bibles, spread open wide through the centuries, one is fain to regret the visible majesty of the granite writing, those gigantic alphabets in the shape of colonnades, porches, and obelisks; these mountains, as it were, the work of mans hand spread over the whole world and filling the past, from the pyramid to the steeple, from Cheops to Strassburg. The past should be read in these marble pages; the books written by architecture can be read and reread, with never-diminishing interest; but one cannot deny the grandeur of the edifice which printing has raised in its turn. -That edifice is colossal. I do not know what statistician it was who calculated that by piling one upon another all the volumes issued from the press since Gutenberg, you would bridge the space between the earth and the moonbut it is not to that kind of greatness we allude. Nevertheless, if we try to form a collective picture of the combined results of printing down to our own times, does it not appear as a huge structure, having the whole world for foundation, and the whole human race for its ceaselessly active workmen, and whose pinnacles tower up into the impenetrable mist of the future? It is the swarming ant-hill of intellectual forces; the hive to which all the golden-winged messengers of the imagination return, laden with honey. This prodigious edifice has a thousand storeys, and remains forever incomplete. The press, that giant engine, incessantly absorbing all the intellectual forces of society, disgorges, as incessantly, new materials for its work. The entire human race is on the scaffolding; every mind is a mason. Even the humblest can fill up a gap, or lay another brick. Each day another layer is put on. Independently of the individual contribution, there are certain collective donations. The eighteenth century presents the Encyclopedia, the Revolution the Moniteur. Undoubtedly this, too, is a structure, growing and piling itself up in endless spiral lines; here, too, there is confusion of tongues, incessant activity, indefatigable labour, a furious contest between the whole of mankind, an ark of refuge for the intelligence against another deluge, against another influx of barbarism. -It is the second Tower of Babel. -BOOK VI -Chapter 1 - An Impartial Glance at the Ancient Magistracy -A Mighty fortunate personage in the year of grace 1482, was the noble knight, Robert dEstouteville, Sieur of Beyne, Baron of Ivry and Saint-Andry in the March, Councillor and Chamberlain to the King, and Warden of the Provostry of Paris. It was well-nigh seventeen years ago since he had received from the King, on November 7, 1465the year of the comet1this fine appointment of Provost of Paris, reputed rather a seigneurie than an office. Dignitas, says Joannes L?mn?us, qu?, cum non exigua potestate politiam concernente, atque pr?rogativis multis et juribus conjuncta est.2 It was indeed a thing to marvel at that in 1482 a gentleman should be holding the Kings commission, whose letters of appointment dated back to the date of the marriage of a natural daughter of Louis XI with Monsieur the Bastard of Bourbon. On the same day on which Robert dEstouteville had replaced Jacques de Villiers in the Provostry of Paris, Ma?tre Jehan Dauvet superseded Messire Hlye de Thorrettes as Chief President of the Court of Parliament, Jehan Jouvenel des Ursins supplanted Pierre de Morvilliers in the office of Chancellor of France, and Regnault des Dormans turned Pierre Puy out of the post of Master of Common Pleas to the royal palace. But over how many heads had that Presidency, that Chancellorship, and that Mastership passed since Robert dEstouteville held the Provostship of Paris! It had been given unto his keeping, said the letters patent; and well indeed had he kept the same. He had clung to it, incorporated himself into it, had so identified himself with it that he had managed to escape that mania for change which so possessed Louis XI, a close-fisted, scheming king, who sought to maintain, by frequent appointments and dismissals, the elasticity of his power. Furthermore, the worthy knight had procured the reversion of his post for his son, and for two years now the name of the noble M. Jacques dEstouteville, Knight, had figured beside that of his father at the head of the roll of the Provostry of Parisin truth, a rare and signal favour! To be sure, Robert dEstouteville was a good soldier, had loyally raised his banner for the King against the League of the Public Weal, and on the entry of the Queen into Paris in 14 had presented her with a wonderful stag composed of confectionery. Besides this, he was on a very friendly footing with Messire Tristan lHermite, Provost-Marshal of the Kings palace. So Messire Roberts existence was an easy and pleasant one. First of all, he enjoyed very good pay, to which were attached and hanging like extra grapes on his vine, the revenues from the civil and criminal registries of the Provostry, the revenues, civil and criminal, accruing from the auditory courts of the Chatelet, not to speak of many a comfortable little toll-due from the bridges of Mantes and Corbeil, and the profits from the taxes levied on the grain-dealers, as on the measurers of wood and salt. Add to this, the pleasure of displaying on his official rides through the cityin shining contrast to the party-coloured gowns, half red, half tan, of the sheriffs and district officershis fine military accoutrements, which you may admire to this day, sculptured on his tomb in the Valmont Abbey in Normandy, and his morion with all the bruises in it got at Montlhry. Then, it was no mean thing to have authority over the constables of the Palais de Justice, over the warder and the Commandant of the Chatelet, the two auditors of the Chatelet (auditores Castelleti), the sixteen commissioners of the sixteen districts, the jailer of the Chatelet, the four enfeoffed officers of the peace, the hundred and twenty mounted officers of the peace, the hundred and twenty officers of the rod, the captain of the watch with his patrol, his under-patrol, his counter-and-night-patrol. Was it nothing to exercise supreme and secondary jurisdiction, to have the right of pillory, hanging, and dragging at the carts tail, besides minor jurisdiction in the first resort (in prima instantia, as the old charters have it) over the whole viscomty of Paris, so gloriously endowed with the revenues of seven noble bailiwicks? Can you conceive of anything more gratifying than to mete out judgment and sentence, as Messire Robert dEstouteville did every day in the Grand Chatelet, under the wide, low-pitched Gothic arches of Philip Augustus; and to retire, as he was wont, every evening to that charming house in Rue Galile, within the purlieus of the Palais Royal, which he held by right of his wife, Dame Ambroise de Lor, where he could rest from the fatigues of having sent some poor devil to pass the night on his part in that little cell of the Rue de lEscorcherie, which the provosts and sheriffs of Paris frequently used as a prisonthe same measuring eleven feet in length, seven feet and four inches in width, and eleven feet in height?3 -And not only had Messire Robert dEstouteville his special jurisdictional offices as Provost of Paris, but also he had his seat, with power over life and death, in the Kings Supreme Court. There was no head of any account but had passed through his hands before falling to the executioner. It was he who had fetched the Comte de Nemours from the Bastille Saint-Antoine, to convey him to the Halles; he who had escorted the Comte de Saint-Pol to the Place de Grve, who stormed and wept, to the huge delight of Monsieur the Provost, who bore no love to Monsieur the Constable. -Here, assuredly, was more than sufficient to make a mans life happy and illustrious and to merit some day a noteworthy page in that interesting chronicle of the Provosts of Paris, from which we learn that Oudard de Villeneuve owned a house in the Rue des Boucherie, that Guillaume de Hangast bought the great and the little Savoie mansion, that Guillaume Thiboust gave his houses in the Rue Clopin to the Sisters of Sainte-Genevive, that Hugues Aubriot lived in the H?tel du Porc-epic, and other facts of a domestic character. -Nevertheless, in spite of all these reasons for taking life easily and pleasantly, Messire Robert dEstouteville had risen on the morning of January 7, 1482; feeling as sulky and dangerous in temper as a bear with a sore head; why, he would have been at a loss to say. Was it because the sky was gloomy? because the buckle of his old sword-beltanother relic of Montlhrywas clasped too tight, and girded up his fair, round, provostorial port in all too military a fashion? or because he had just seen a band of tattered varlets, who had jeered at him as they passed below his windows walking four abreast, in doublets without shirts, in hats without brims, and wallet and bottle hanging at their sides? Or was it the vague premonition of the loss of those three hundred and seventy livres, sixteen sols, eight deniers, of which in the following year the future King Charles VIII was going to dock the revenues of the Provostry? The reader may take his choice, but for our part we are inclined to the opinion that he was in a bad temper becausehe was in a bad temper. -Besides, it was the day after a holiday, a day distasteful to everybody, especially to the magistrate whose business it was to sweep up all the dirtliterally and figurativelywhich a Paris holiday inevitably brings with it. Then, too, he was to sit that day at the Grand Chatelet; and we have noticed that the judges generally manage that their day of sitting shall also be their day of ill-humour, in that they may have some one on whom conveniently to vent their spleen in the name of the King, justice, and the law. -The sitting, however, had begun without him. His deputies in civil, criminal, and private causes were acting for him as usual; and by eight oclock in the morning, some scores of townsfolk, men and women, crowded up between the wall and a strong barrier of oak in a dark corner of the court of the Chatelet, were blissfully assisting at the varied and exhilarating spectacle of the law, civil and criminal, as administered by Ma?tre Florian Barbedienne, examining judge at the Chatelet, and deputy for Monsieur the Provost, an office he performed in a manner somewhat mixed and altogether haphazard. -The hall was small, low, and vaulted, furnished at the far end with a table figured over with fleur de lis, a great, carved oak chair for the Provost, and therefore empty, and a stool at the left side for Ma?tre Florian. Lower down sat the clerk, scribbling fast. Opposite to them were the people; while before the door and before the table were stationed a number of sergeants of the Provostry, in violet woollen jerkins, with white crosses on their breasts. Two sergeants of the Common Hall in their All-Saints jacketshalf red, half bluestood sentinel at a low, closed door which was visible in the background behind the table. A solitary Gothic window, deeply embedded in the wall, shed the pale light of a January morning on two grotesque figuresthe whimsical stone devil, carved on the keystone of the vaulted ceiling, and the judge sitting at the back of the Hall bending over the fleur de lis of the table. -Picture to yourself that figure at the table, leaning on his elbows between two bundles of documents, his foot wrapped in the tail of his plain brown gown, the face in its frame of white lambskin, of which the eye-brows seem to be a piece red, scowling, blinking, carrying with dignity the load of fat that met under his chinand you have Ma?tre Florian Barbedienne, examining judge at the Chatelet. -Now, Ma?tre Florian was deafrather a drawback for an examining judgebut none the less did he mete out judgment without appeal and with great propriety. Surely it is sufficient that a judge should appear to listen, and the venerable auditor the better filled this conditionthe sole essential to the good administration of justicein that his attention could not be distracted by any sound. -However, he had among the onlookers a merciless critic of deeds and manners in the person of our friend, Jehan Frollo of the Mill, the little scholar of yesterdays scenes, the little loafer one was certain to encounter anywhere in Paris, save in the lecture-room of the professors. -Look, whispered he to his companion, Robin Poussepain, who sat beside him in fits of suppressed laughter at his comments on the scene before them, why, theres Jehanneton du Buisson, the pretty lass of that old lazy-bones at the March-Neuf! On my soul, he means to fine her, the old dotard! Fifteen sols, four deniers parisis for wearing two rosaries! Thats rather dear! Lex duri carminiswhos this? Robin Chief-de-Ville, hauberk-maker, for being passed and admitted a master in the said craft. Ah! his entrance fee. What! Two gentlemen among this rabble! Aiglet de Soins, Hutin de Mailly, two squires! Corpus Christi! Oh, for throwing dice! When shall we see our Rector here, I wonder? A fine of a hundred livres parisis to the King! Barbedienne lays about him, like a deaf oneas he is. May I be my brother the archdeacon, if that shall hinder me from playing; from playing by day, and playing by night, living at play, dying at play, and staking my soul after I have staked my shirt! Holy Virgin! what a lot of girls! One at a time, my lambkins! Ambroise Lcuyre, Isabeau la Paynette, Brarde Gironin! By heavens, I know them all! A fine! a fine! ten sols parisis; thatll teach you minxes to wear gilded girdles! Oh, the ancient sheeps-head of a judge, deaf and doting! Ah, Florian thou dolt! Oh, Barbedienne thou booby! Do but look at him there at tablehe dines off the litiganthe dines off the casehe eatshe chewshe gobbleshe fills himself! Fines, unclaimed goods, dues, costs, expenses, wages, damages, torture, imprisonment, and pillory and fetters, and loss of rightall are to him as Christmas comfits and midsummer marchpane! Look at him, the swine! Good! it begins again. Another light o love! Thibaude-la-Thibaude, as I live! For having come out of the Rue Glatigny! Whos this young shaver? Gieffroy Mabonne, cross-bowman. He blasphemed the name of God the Father. Thibaude a fine! Gieffroy a fine! A fine for both of them! The deaf old blockhead, he is sure to have mixed up the two. Ten to one that he makes the girl pay for the oath, and the soldier for the amour! Attention, Robin Poussepain! Who are they bringing in now? What a crowd of tip-staffs! By Jupiter, the whole pack of hounds! This must be the grand catch of the day. A wild boar at least. It is one Robin! it isand a fine specimen too! Hercules! it is our prince of yesterday, our Pope of Fools, our bell-ringer, our hunchback, our grimace! It is Quasimodo! -It was indeed. -It was Quasimodo, bound about with cords, tightly pinioned, and under a strong guard. The detachment of officers surrounding him was led by the Captain of the watch in person, with the arms of France embroidered on his breast, and those of the City of Paris on his back. However, apart from his ugliness, there was nothing about Quasimodo to warrant this show of halberds and arquebuses. He was moody, silent, and composed, only casting from time to time a sullen and angry glance out of his one eye at the cords that bound him. He cast this same glance at his surroundings, but it was so dazed and drowsy that the women only pointed him out in derision to one another. -Meanwhile, Ma?tre Florian was busy turning over the pages of the charge drawn up against Quasimodo, handed to him by the clerk, and, having glanced at it, seemed to commune with himself for a moment. Thanks to this precaution, which he was always careful to employ before proceeding with his examination, he knew in advance the name, quality, and offence of the delinquent, made prearranged replies to foreseen questions, and contrived to find his way through all the sinuosities of the cross-examination without too openly betraying his deafness. The written charge was to him as the dog to the blind man. If it happened, now and then, that his infirmity became evident through some unintelligible address, or some question wide of the mark, it passed with some for profundity, and with others for imbecility. In either case, the honour of the magistracy underwent no diminution: better far that a judge should be reputed imbecile or profound rather than deaf. He therefore took such precautions to conceal his deafness from others, and usually succeeded so well, that he had come at last to deceive himself on the subjectan easier matter than one might suppose: for all hunchbacks walk with head erect; all stammerers are fond of talking; deaf people invariably speak in a whisper. For his part, he thought, at most, that perhaps his ear was a trifle less quick than other peoples. This was the sole concession he would make to public opinion in his rare moments of candour and self-examination. -Having then ruminated well on Quasimodos case, he threw back his head and half-closed his eyes, by way of extra dignity and impartiality, with the result that, for the moment, he was both blind and deafa twofold condition without which no judge is really perfect. -In this magisterial attitude he commenced his examination. -Your name? -Now here was a case which had not been provided for by the lawthe interrogation of one deaf person by another in similar plight. -Quasimodo, who had no hint of the fact that he was being addressed, continued to regard the judge fixedly, but made no reply. The judge, deaf himself, and unaware of the deafness of the accused, imagined he had answered, as accused persons generally did, and continued with his usual stupid and mechanical self-confidence: -Very goodyour age? -Quasimodo made no answer to this question either, but the judge, fancying he had done so, went on: -Now, your calling? -Continued silence. The bystanders, however, began to whisper and look at each other. -That will do, returned the imperturbable magistrate when he concluded that the accused had finished his third answer. You stand charged before us, primo, with nocturnal disturbance; secundo, with unjustifiable violence to the person of a light woman, in prejudicium meretricis; tertio, of rebellion and contempt against the archers of our Lord the King. Explain yourself on these points.Clerk, have you written down what the accused has said so far? -At this unlucky question there was an explosion of laughter, beginning with the clerk and spreading to the crowdso violent, so uncontrollable, so contagious, so universal, that neither of the deaf men could help perceiving it. Quasimodo turned round and shrugged his high shoulders disdainfully, while Ma?tre Florian, as surprised as he, and supposing that the laughter of the spectators had been provoked by some unseemly reply from the accused, rendered visible to him by that shrug, addressed him indignantly: -Fellow, that last answer of yours deserves the halter. Do you know to whom you are speaking? -This sally was hardly calculated to extinguish the outburst of general hilarity. The thing was so utterly absurd and topsy-turvy, that the wild laughter seized even the sergeants of the Common Hall, a sort of pikemen whose stolidity was part of their uniform. Quasimodo alone preserved his gravity, for the very good reason that he had no idea what was occurring round him. The judge, growing more and more irritated, thought it proper to continue in the same tone, hoping thereby to strike such terror to the heart of the prisoner as would react on the audience and recall them to a sense of due respect. -It would seem, then, headstrong and riotous knave that you are, that you would dare to flout the auditor of the Chatelet; the magistrate entrusted with the charge of the public safety of Paris; whose duty it is to search into all crimes, delinquencies, and evil courses; to control all trades and forbid monopolies; to repair the pavements; to prevent the retail hawking of poultry and game, both feathered and furred; to superintend the measuring of firewood and all other kinds of wood; to purge the city of filth, and the air of all contagious distemperin a word, to slave continually for the public welfare without fee or recompense, or hope of any. Know you that my name is Florian Barbedienne, deputy to Monsieur the Provost himself, and, moreover, commissioner, investigator, controller, and examiner, with equal power in provostry, bailiwick, registration, and presidial court -There is no earthly reason why a deaf man talking to a deaf man should ever stop. God alone knows where and when Ma?tre Florian would have come to anchor, once launched in full sail on the ocean of his eloquence, had not the low door at the back of the hall suddenly opened, and given passage to Monsieur the Provost in person. -At his entrance Ma?tre Florian did not stop, but wheeling half round, and suddenly aiming at the Provost the thunder-bolts which up to now he had launched at Quasimodo: -Monseigneur, he said, I demand such penalty as shall seem fitting to you against the accused here present for flagrant and unprecedented contempt of court. -He seated himself breathless, wiping away the great drops that fell from his forehead and splashed like tears upon the documents spread out before him. Messire Robert dEstouteville knit his brows and signed to Quasimodo with a gesture so imperious and significant, that the deaf hunchback in some degree understood. -The Provost addressed him sternly: What hast thou done, rascal, to be brought hither? -The poor wretch, supposing that the Provost was asking his name, now broke his habitual silence and answered in hoarse, guttural tones, Quasimodo. -The answer corresponded so little with the question that the former unbridled merriment threatened to break out again, and Messire Robert, crimson with anger, roared, Dost dare to mock me too, arch-rogue? -Bell-ringer of Notre-Dame, continued Quasimodo, thinking that he must explain to the judges who he was. -Bell-ringer! returned the Provost, who, as we know, had risen that morning in so vile a temper that there was no need to add fresh fuel to the fire by such unwarrantable impudence. Bell-ringer indeed! They shall ring a carillon of rods on thy back at every street corner of Paris. Hearest thou, rascal? -If it is my age you desire to know, said Quasimodo, I think I shall be twenty come Martinmas. -This was going too far; the Provost could contain himself no longer. -Ha, miserable knave, thou thinkest to make sport of the law! Sergeant of the rod, you will take this fellow to the pillory in the Grve and there flog him and turn him for an hour. He shall pay for this, tte-Dieu! And I command that this sentence be proclaimed by means of the four legally appointed trumpeters at the seven castellanies of the jurisdiction of Paris. -The clerk proceeded forthwith to put the sentence on record. -Ventre-Dieu! I call that giving judgment in good style! said little Jehan Frollo of the Mill, from his secluded corner. -The Provost turned and again transfixed Quasimodo with blazing eye. I believe the rascal said Ventre-Dieu! Clerk, you will add twelve deniers parisis as a fine for swearing, and let one-half of it go to the Church of Saint-Eustache. I have a particular devotion for Saint-Eustache. -A few minutes later and the sentence was drawn up. The language was brief and simple. The legal procedure of the Provostry and bailiwick of Paris had not yet been elaborated by the President, Thibaut Baillet, and Roger Barmne, Kings advocate, and therefore not yet obscured by that forest of chicanery and circumlocution planted in it by these two lawyers at the beginning of the sixteenth century. All was still clear, rapid, and to the point. There was no beating about the bush, and straight before you, at the end of every path, you had a full view of the wheel, the gibbet, or the pillory. You knew, at least, exactly where you were. -The clerk presented the sentence to the Provost, who affixed his seal to it and then departed, to continue his round through the several courts of law, in a frame of mind which seemed likely, for that day, to fill every jail in Paris. Jehan Frollo and Robin Poussepain were laughing in their sleeve, while Quasimodo regarded the whole scene with an air of surprise and indifference. -Nevertheless, the clerk, while Ma?tre Florian was engaged in reading over the judgment before signing it in his turn, felt some qualms of compassion for the poor devil under sentence, and in the hope of obtaining some mitigation of his penalties, bent as near as he could to the examiners ear, and said, pointing to Quasimodo, The man is deaf. -He hoped that the knowledge of a common infirmity would awaken Ma?tre Florians interest in favour of the condemned. But in the first place, as we have already explained, Ma?tre Florian did not like to have his deafness commented upon; and secondly, that he was so hard of hearing that he did not catch one word the clerk was saying. Desiring, however, to conceal this fact, he replied: Ah! that makes all the difference. I did not know that. In that case, one more hour of pillory for him. And, this modification made, he signed the sentence. -And serve him right too, said Robin Poussepain, who still owed Quasimodo a grudge; thatll teach him to handle folks so roughly. -______________________ -1 This comet, for deliverance from which, Pope Calixtus, uncle to Borgia, ordered public prayer, is the same which reappeared in 1835.Authors Note. -2 A dignity to which is attached no little power in dealing with the public safety, together with many prerogatives and rights. -3 Crown accounts, 1383.Authors Note. -Chapter 2 - The Rat-hole -With the readers permission we will now return to the Place de Grve, which was quitted yesterday with Gringoire, to follow Esmeralda. -It is ten in the morning, and everywhere are the unmistakable signs of the day after a public holiday. The ground is strewn with dbris of every description, ribbons, rags, plumes, drops of wax from the torches, scraps from the public feast. A good many of the townsfolk are loafing aboutas we would say to-dayturning over the extinguished brands of the bonfire, standing in front of the Maison aux Piliers rapturously recalling the fine hangings of the day before, and gazing now at the nails which fastened themlast taste of vanished joywhile the venders of beer and cider roll their casks among the idle groups. A few pass to and fro, intent on business; the tradespeople gossip and call to one another from their shop doors. The Festival, the Ambassadors, Coppenole, the Pope of Fools, are in every mouth, each vying with the other as to who shall make the wittiest comments and laugh the loudest; while four mounted officers of the peace, who have just posted themselves at the four corners of the pillory, have already drawn away a considerable portion of the idlers scattered about the square, who cheerfully submit to any amount of tediousness and waiting, in expectation of a little exhibition of Justice. -If now, after contemplating this stirring and clamorous scene which is being enacted at every corner of the Place, the reader will turn his attention towards the ancient buildinghalf Gothic, half Romanesquecalled the Tour-Roland, forming the western angle of the quay, he will notice, at one of its corners, a large, richly illuminated breviary for the use of the public, protected from the rain by a small pent-house and from thieves by a grating, which, however, allows of the passer-by turning over the leaves. Close beside this breviary is a narrow, pointed window looking on to the square and closed by an iron cross-bar, the only aperture by which a little air and light can penetrate to a small, doorless cell constructed on the level of the ground within the thickness of the wall of the old mansion and filled with a quiet the more profound, a silence the more oppressive, that a public square, the noisiest and most populous in Paris, is swarming and clamouring round it. -This cell has been famous in Paris for three centuries, ever since Mme. Rolande of the Tour-Roland, mourning for her father who died in the Crusades, had caused it to be hollowed out of the wall of her house and shut herself up in it forever; retaining of all her great mansion but this one poor chamber, the door of which was walled up and the window open to the elements winter and summer, and giving the rest of her possessions to the poor and to God. The inconsolable lady had lingered on for twenty years awaiting death in this premature tomb, praying night and day for the soul of her father, making her bed on the cold ground without even a stone for a pillow, clothed in sackcloth, and living only upon such bread and water as the compassionate might deposit on the ledge of her windowthus receiving charity after bestowing it. At her death, at the moment of her passing to another sepulchre, she had bequeathed this one in perpetuity to women in afflictionmothers, windows, or maidenswho should have many prayers to offer up on behalf of others or of themselves, and should choose to bury themselves alive for some great grief or some great penitence. The poor of her time had honoured her funeral with tears and benedictions; but, to their great regret, the pious lady had been unable to receive canonization for lack of interest in the right quarter. Nevertheless, those among them who were not quite so pious as they should have been, trusting that the matter might be more easily arranged in heaven than in Rome, had frankly offered up their prayers for the deceased to God himself, in default of the Pope. The majority, however, had contented themselves with holding Rolandes memory sacred, and converting her rags into relics. The town, for its part, had founded, in pursuance of the ladys intention, a public breviary, which had been permanently fixed beside the window of the cell, that the passer-by might halt there for a moment, if only to pray; that prayer might suggest almsgiving, and thus the poor recluses, inheriting the stone cell of Mme. Rolande, be saved from perishing outright of hunger and neglect. -These living tombs were by no means rare in the cities of the Middle Ages. Not infrequently, in the very midst of the busiest street, the most crowded, noisy market-place, under the very hoofs of the horses and wheels of the wagons, you might come upon a vault, a pit, a walled and grated cell, out of the depths of which a human being, voluntarily dedicated to some everlasting lamentation, or some great expiation, offered up prayer unceasingly day and night. But all the reflections that such a strange spectacle would awaken in us at the present day; that horrible cell, a sort of intermediate link between the dwelling and the grave, between the cemetery and the city; that living being cut off from the communion of mankind and already numbered with the dead; that lamp consuming its last drop of oil in the darkness; that remnant of life flickering out in the pit; that whisper, that voice, that never-ending prayer encased in stone: that eye already illumined by another sun; that ear inclined attentive to the walls of a tomb; that soul imprisoned in a body, itself a prisoner within that dungeon, and from out that double incarnation of flesh and stone, the perpetual plaint of a soul in agonynothing of all this reached the apprehension of the crowd. The piety of that day, little given to analyzing or subtle reasoning, did not regard a religious act from so many points of view. It accepted the thing as a whole, honoured, lauded, and, if need be, made a saint of the sacrifice, but did not dwell upon its sufferings nor even greatly pity it. From time to time the charitable world brought some dole to the wretched penitent, peered through the window to see if he yet lived, was ignorant of his name, scarcely knew how many years ago he had begun to die, and to the stranger who questioned them respecting the living skeleton rotting in that cave, they would simply answer: It is the recluse. -This was the way they looked at things in those days, without metaphysics, neither enlarging nor diminishing, with the naked eye. The microscope had not been invented yet for the examination either of material or spiritual objects. -Examples of this kind of living burial in the heart of the town were, although they excited but little remark, frequently to be met with, as we have said before. In Paris there was a considerable number of these cells of penitence and prayer, and nearly all of them were occupied. It is true the clergy took particular care that they should not be left empty, as that implied lukewarmness in the faithful; so when penitents were not to the fore, lepers were put in instead. Besides the cell at the Grve, already described, there was one at Montfaucon, one at the charnel-house of the Innocents, another, I forget just whereat the Logis-Clichon, I fancy; and others at many different spots, where, in default of monuments, their traces are still to be found in tradition. The University certainly had one; on the hill of Saint-Germain a sort of medi?val Job sat for thirty years, singing the penitential psalms on a dung-heap at the bottom of a dry well, beginning anew as soon as he came to the end, and singing louder in the night-timemagna voce per umbras; and today the antiquary still fancies that he hears his voice as he enters the Rue du Puits-qui-parle: the street of the Talking Well. -To confine ourselves here to the cell in the Tour-Roland, we confess that it had seldom lacked a tenantsince Mme. Rolandes death it had rarely been vacant, even for a year or two. Many a woman had shut herself up there to weep until death for her parents, her lovers, or her frailties. Parisian flippancy, which will meddle with everything, especially with such as are outside its province, declared that very few widows had been observed among the number. -After the manner of the period, a Latin legend inscribed upon the wall notified to the lettered wayfarer the pious purpose of the cell. This custom of placing a brief distinguishing motto above the entrance to a building continued down to the middle of the sixteenth century. Thus, in France, over the gateway of the prison belonging to the Manor-house of Tourville, stands, Sileto et spera; in Ireland, under the escutcheon above the great gateway of Fortescue Castle, Forte scutum, salus ducum; and in England, over the principal entrance of the hospitable mansion of the Earls Cowper, Tuum est. For in those days every edifice expressed a special meaning. -As there was no door to the walled-up cell of the Tour-Roland, they had engraved above the window in great Roman characters the two words: -TU, ORA1 -Whence it came about that the people, whose healthy common sense fails to see the subtle side of things, and cheerfully translates Ludovico Magno by Porte Saint-Denis, had corrupted the words over this dark, damp, gloomy cavity into Trou-aux-rats, or Rat-Holca rendering less sublime perhaps than the original; but, on the other hand, decidedly more picturesque. -______________________ -1 Pray thou. -Chapter 3 - The Story of a Wheaten Cake -At the time at which the events of this story occurred, the cell of the Tour-Roland was occupied, and if the reader desires to know by whom, he has only to listen to the conversation of three worthy gossips, who, at the moment when we attracted his attention to the Rat-Hole, were directing their steps to that very spot, going along the river-side from the Chatelet towards the Place de Grve. -Two of these women were dressed after the fashion of the good burgher wives of Paris; their fine white gorgets, striped red and blue woollen kirtles, white knitted hose with embroidered clocks, trimly pulled up over their legs, their square-toed shoes of tan-coloured leather with black soles, and above all, their head-dressa sort of tinsel-covered horn, loaded with ribbons and lace, still worn by the women of Champagne, and the Grenadiers of the Russian imperial guardproclaimed them to belong to that class of rich tradeswomen who hold the medium between what servants call a woman and what they call a lady. They wore neither rings nor gold crosses; but it was easy to perceive that this was owing not to poverty, but simply out of fear of the fine incurred by so doing. Their companions dress was very much the same; but there was in her appearance and manner an indefinable something which betrayed the wife of the country notary. Her way of wearing her girdle so high above her hips would alone have proved that it was long since she had been in Paris, without mentioning that her gorget was plaited, that she wore knots of ribbon on her shoes, that the stripes of her kirtle ran round instead of down, and a dozen other crimes against the prevailing mode. -The first two walked with that air peculiar to Parisiennes showing the town to country cousins. The countrywoman held by the hand a chubby little boy, who in his hand held a big wheaten cakeand we regret to have to add that, owing to the inclemency of the weather, he was using his tongue as a pocket-handkerchief. -The boy let himself be dragged alongnon passibus ?quis, as Virgil sayswith uneven steps, stumbling every minute, to the great annoyance of his mother. It is true that he looked oftener at the cake than on the ground. Some very serious reason must have prevented him from biting into the cake, for he contented himself with merely gazing at it affectionately. But the mother would have done better to take charge of the tempting morsel herself. It was cruel to make a Tantalus of poor chubby-cheeks. -Meanwhile, the three damoiselles (for the title of dame was reserved then for the women of noble birth) were all talking at once. -We must hasten, Damoiselle Mahiette, said the youngest of the three, who was also the fattest, to their country friend. I fear me we shall be too late. They told us at the Chatelet that he was to be carried to the pillory immediately. -Ahbah! What are you talking about, Damoiselle Oudarde Musnier? returned the other Parisienne. He will be a good two hours on the pillory. We have plenty of time. Have you ever seen anybody pilloried, my dear Mahiette? -Yes, said Mahiette, at Reims. -Pooh! whats your pillory at Reims? A paltry cage where they put nobody but clowns! Thats not worth calling a pillory! -Nobody but clowns! cried Mahiette. In the Cloth-Market at Reims! Let me tell you, we have had some very fine criminals therewho had killed father and mother! Clowns indeed! What do you take me for, Gervaise? -And there is no doubt the country lady was on the point of flying into a rage for this disparagement of her pillory, but fortunately the discreet Damoiselle Oudarde Musnier turned the conversation in time. -By-the-bye, Damoiselle Mahiette, what think you of our Flemish Ambassadors? Have you any as grand at Reims? -I must confess, answered Mahiette, that its only in Paris you see such Flemings as these. -Did you see among the embassy that great Ambassador whos a hosier? asked Oudarde. -Yes, said Mahiette, he looks like a Saturn. -And that fat one, with a face like a bare paunch, Gervaise went on; and the little one, with small, blinking eyes and red eye-lids with half the lashes pulled out like a withered thistle? -But their horses are a treat to look at, said Oudarde, all dressed after the fashion of their country! -Ah, my dear, interrupted country Mahiette, assuming in her turn an air of superiority, what would you have said then, if you had seen the horses of the Princess and the whole retinue of the King at the coronation at Reims in 61twenty-one years ago! Such housings and caparisons! Some of Damascus cloth, fine cloth of gold, and lined with sable fur; others of velvet and ermine; others heavy with goldsmiths work and great tassels of gold and silver! And the money that it must all have cost! And the beautiful pages riding them! -But for all that, replied Damoiselle Oudarde dryly, the Flemings have splendid horses; and yesterday a sumptuous supper was given them by Monsieur the Provost-Merchant at the H?tel-de-Ville, at which sweetmeats, and hippocras, and spices, and the like delicacies, were set before them. -What are you saying, neighbour! exclaimed Gervaise. -Why, it was with the Lord Cardinal, at the Petit-Bourbon, that the Flemings supped. -Not at all! At the H?tel-de-Ville! -No, it wasntit was at the Petit-Bourbon. -I know that it was at the H?tel-de-Ville, retorted Oudarde sharply, for the very good reason that Doctor Scourable made them a speech in Latin, with which they were very well satisfied. My husband told me, and he is one of the sworn booksellers. -And I know that it was at the Petit-Bourbon, responded Gervaise no less warmly, for I can tell you exactly what my Lord Cardinals purveyor set before them: twelve double quarts of hippocras, white, pale, and red; twenty-four boxes of gilded double marchpanes of Lyons; four-and-twenty was torches of two pounds apiece; and six demi-hogsheads of Beaune wine, both white and yellow, the best that could be procured. I hope thats proof enough! I have it from my husband, whos Captain of the fifty guards at the Chatelet, who only this morning was making a comparison between the Flemish Ambassadors and those of Prester John and the Emperor of Trebizonde, who came to Paris from Mesopotamia and wore rings in their ears. -So true is it that they supped at the H?tel de Ville, replied Oudarde, quite unmoved by this string of evidence, that never was seen so fine a show of meats and delicacies. -I tell you they were served by Le Sec, the town sergeant at the Petit-Bourbon, and that is what has put you wrong. -At the H?tel-de-Ville, I say. -At the Petit-Bourbon, my dear! And whats more, they lit up the word Hope, which stands over the great doorway, with fairy glasses. -At the H?tel-de-Ville! At the H?tel-de-Ville!for Husson le Voir played the flute to them. -I tell you, no! -I tell you, yes! -I tell you, no! -The good, fat Oudarde was preparing to reply, and the quarrel would no doubt have ended in the pulling of caps, had not Mahiette suddenly made a diversion by exclaiming: -Look at those people gathered over there at the end of the bridge. Theres something in the middle of the crowd that theyre looking at. -True, said Gervaise. I hear a tambourine. I think it must be little Esmeralda doing tricks with her goat. Quick, Mahiette, mend your pace and bring your boy! You came to see the sights of Paris. Yesterday you saw the Flemings; to-day you must see the gipsy. -The gipsy! cried Mahiette, turning round and clutching her boy by the arm. God preserve us! She might steal my child! Come, Eustache! -And she set off running along the quay towards the Grve till she had left the bridge far behind her. Presently the boy, whom she dragged rapidly after her, stumbled and fell on his knees. She drew up breathless, and Oudarde and Gervaise were able to join her. -That gipsy steal your child! said Gervaise. What a very strange notion! -Mahiette shook her head thoughtfully. -The strange thing about it, observed Oudarde, is that the sachette has the same notion about the Egyptian women. -The sachette? asked Mahiette. What is that? -Why, Sister Gudule, to be sure, answered Oudarde. -And who is Sister Gudule? -It is very evident that you have lived in Reims not to know that! exclaimed Oudarde. That is the nun in the Rat-Hole. -What? said Mahiette, not the poor woman we are taking this cake to? -Oudarde nodded. Yes, the very one. You will see her directly at her window looking on the Grve. She thinks the same as you about these vagabonds of Egypt that go about with their tambourines and fortune-telling. Nobody knows why she has this abhorrence of Zingari and Egyptians. But you, Mahiette, why should you run away at the mere sight of them? -Oh, answered Mahiette, clasping her boys fair head to her bosom, I would not have that happen to me that happened to Paquette la Chantefleurie. -Oh, you must tell us that story, my good Mahiette, said Gervaise, taking her arm. -Willingly, returned Mahiette, but it is very evident that you have lived in Paris not to know it! Well, you must knowbut there is no need for us to stand still while I tell you the storythat Paquette la Chantefleurie was a pretty girl of eighteen when I too was onethat is to say, eighteen years agoand has had only herself to blame if shes not, like me, a buxom, hearty woman of six-and-thirty, with a husband and a fine boy. But there!from the time she was fourteen it was too late! I must tell you, then, that she was the daughter of Guybertaut, a boat-minstrel at Reims, the same that played before King Charles VII at his coronation, when he went down our river Vesle from Sillery to Muison, and had Mme. la Pucellethe Maid of Orleansin the same boat with him. The old father died when Paquette was quite little, so she had only her mother, who was sister to M. Pradon, a master-brasier and tinsmith in Paris, Rue Parin-Garlin, and who died last yearso you see, she was of good family. The mother was a simple, easy-going creature, unfortunately, and never taught her anything really usefuljust a little needlework and toy-making, which did not prevent her growing tall and strong, and remaining very poor. They lived together at Reims, by the river-side, in the Rue de Folle-Peinemark that!for I believe that is what brought trouble to Paquette. Well, in 61the year of the Coronation of our King Louis XI, whom God preserve!Paquette was so gay and so fair that she was known far and wide as La Chantefleuriepoor girl! She had pretty teeth, and she was fond of laughing, to show them. Now, a girl who is overfond of laughing is well on the way to tears; pretty teeth are the ruin of pretty eyesand thus it befell Chantefleurie. She and her mother had a hard struggle to gain a living; they had sunk very low since the fathers deaththeir needlework brought them in barely six deniers a week, which is not quite two liards. Time was when Guybertaut had got twelve sols parisis at a coronation for a single song! One winterit was that same year of 61the two women had not a log or a fagot, and it was very cold, and this gave Chantefleurie such a beautiful colour in her cheeks that the men all looked after her and she was ruined.Eustache! just let me see you take a bite out of that cake!We saw in a moment that she was ruined when one Sunday she came to church with a gold cross on her neck, At fourteenwhat do you say to that? The first was the young Vicomte de Cormontreuil, whose castle is about three-quarters of a mile from Reims; then it was Messire Henri de Triancourt, the Kings outrider; then, coming down the scale, Chiart de Beaulion, a man-at-arms; then, still lower, Guery Aubergeon, kings carver; then Mac de Frpus, barber to Monsieur the Dauphin; then Thvenin le Moine, one of the royal cooks; then, still going down, from the young to the old, from high to low birth, she fell to Guillaume Racine, viol player, and to Thierry de Mer, lamp-maker. After that, poor Chantefleurie, she became all things to all men and had come to her last sou. What think you, damoiselles, at the coronation, in that same year 61, it was she who made the bed for the chief of the bawdies!in that same year! Mahiette sighed and wiped away a tear. -But I see nothing so very extraordinary in this story, said Gervaise, and there is no word either of Egyptians or children. -Patience, returned Mahiette; as for the child, I am just coming to that. In 66, sixteen years ago this month, on Saint-Pauls day, Paquette was brought to bed of a little girl. Poor creature, she was overjoyedshe had long craved to have a child. Her mother, foolish woman, who had never done anything but close her eyes to what was going on, her mother was dead. Paquette had no one in the world to love or to love her. For the five years since she had fallen, poor Paquette had been a miserable creature. She was alone, all alone in the world, pointed at, shouted at through the streets, beaten by the sergeants, and jeered at by little ragged boys. Besides, she was already twenty, and twenty means old age for a courtesan. Her frailty now began to bring her in no more than did her needlework formerly: for every line in her face she lost a crown in her pocket. Winter came hard to her again, wood was growing scarce in her fire-place and bread in her cupboard. She could not work, because, by giving way to pleasure she had given way to idleness, and she felt hardships the more because by giving way to idleness she had given way to pleasure. At least, that is how Monsieur the Cur of Saint-Rmy explains why those sort of women feel cold and hunger more than other poor females do when they get old. -Yes, observed Gervaise, but about these gipsies? -Wait a moment, Gervaise, said Oudarde, who was of a less impatient temperament; what should we have at the end if everything was at the beginning? Go on, Mahiette, I pray you. Alas, poor Chantefleurie! -Well, Mahiette continued, so she was very sad and very wretched, and her cheeks grew hollow with her perpetual tears. But in all her shame, her infamy, her loneliness, she felt she would be less ashamed, less infamous, less deserted, if only there was something or somebody in the world she could love, or that would love her. She knew it would have to be a child, for only a child could be ignorant enough for that. This she had come to see after trying to love a robberthe only man who would have anything to do with herbut in a little while she found that even the robber despised her. These light-o-loves must needs always have a lover or a child to fill their hearts, or they are most unhappy. As she could not get a lover, all her desire turned towards having a child; and, as she had all along been pious, she prayed unceasingly to God to send her one. So God took compassion on her and sent her a little girl. I will. not try to describe to you her joyit was a passion of tears and kisses and caresses. She suckled it herself, and made swaddling-bands for it out of her coverletthe only one she had upon her bed, but now she felt neither cold nor hunger. Her beauty came back to heran old maid makes a young motherand poor Chantefleurie went back to her old trade and found customers for her wares, and laid out the wages of her sin in swaddling-clothes and bibs and tuckers, lace robes, and little satin capswithout so much as a thought for a new coverlet for herself. -Master Eustache, did I not tell you not to eat that cake? In truth, the little Agns, that was the childs nameits baptismal name, for, as to a surname, it was long since Chantefleurie had lost hersin very truth, the little one was more of a mass of ribbons and broideries than ever a dauphiness of Dauphiny! Among other things, she had a pair of little shoes such as King Louis himself never had the like. Her mother had stitched them and embroidered them herself, bestowing upon them all her art and the ornament that ought more properly to belong to a robe for Our Lady. In good sooth, they were the prettiest little rose-coloured shoes that ever were seen; no longer at most than my thumb, and unless you saw the babes little feet come out of them, you never would have believed that they could get in. To be sure the little feet were so small, so pretty, so rosy!rosier than the satin of the shoes! When you have children of your own, Oudarde, you will know that there is nothing in the world so pretty as those little hands and feet. -I ask nothing better, said Oudarde with a sigh; but I must await the good pleasure of M. Andry Musnier. -However, resumed Mahiette, pretty feet were not the only beauty that Paquettes child possessed. I saw her when she was four months olda chuck!with eyes bigger than her mouth, and beautiful soft, black hair that curled already. She would have made a fine brunette at sixteen! Her mother loved her more day by day. She hugged and kissed and fondled her, washed her, tricked her out in all her finery, devoured herone moment half-crazed, the next thanking God for the gift of this babe. But its pretty rosy feet were her chief delight and wondera very delirium of joy! She was forever pressing her lips to them, forever marvelling at their smallness. She would put them into the little shoes, take them out again, wonder at them, hold them up to the light; she was sorry even to teach them to take a step or two on her bed, and would gladly have passed the rest of her life on her knees, covering and uncovering those little feet, like those of an Infant Jesus. -The tale is all very well, said Gervaise, half to herself; but where is Egypt in all this? -Here, replied Mahiette. One day there came to Reims some very outlandish sort of gentrybeggars and vagabondswandering about the country, led by their dukes and counts. Their faces were sun-burnt, their hair all curling, and they had silver rings in their ears. The women were even more ill-favoured than the men. Their faces were blacker and always uncovered, their only clothing an old woollen cloth tied over their shoulders, and a sorry rocket under that, and the hair hanging loose like a horses tail. The children that scrambled about between their feet would have frightened the monkeys. An excommunicated band! They had come direct from Lower Egypt to Reims by way of Poland. The Pope had confessed them, so they said, and had laid on them the penance of wandering for seven years through the world without ever sleeping in a bed. So they called themselves penitents and stank most horribly. It would seem they had formerly been Saracens, and that is why they believed in Jupiter, and demanded ten livres tournois from all Arch-bishops, Bishops, and Abbots endowed with crosier and mitre. It was a bull of the Pope that got them that. They came to Reims to tell fortunes in the name of the King of Algiers, and the Emperor of Germany. As you may suppose, that was quite enough for them to be forbidden to enter the town. Then the whole band encamped without demur near the Braine gate, upon that mound where theres a wind-mill, close by the old chalk-pits. And of course all Reims was agog to see them. They looked in your hand, and prophesied most wonderful thingsthey were quite bold enough to have foretold to Judas that he would be Pope. At the same time, there were ugly stories about themof stolen children, and cutpurses, and the eating of human flesh. The prudent warned the foolish, and said, Go not near them! and then went themselves by stealth. Everybody was carried away by it. In sober truth, they told you things to have amazed a Cardinal! The mothers made much of their children after the gipsy women had read in their hands all manner of miracles written in Pagan and in Turkish. One had an Emperor, another a Pope, a third a Captain. Poor Chantefleurie caught the fever of curiosity. She wanted to know what she had got, and whether her pretty little Agns would not one day be Empress of Armenia or the like. So she carried her to the Egyptians, and the Egyptian women admired the child, fondled it, kissed it with their black mouths, and were lost in wonder over its little handsalas! to the great joy of its mother. Above all, they were delighted with its pretty feet and pretty shoes. The child was not yet a year old, and was just beginning to prattle a word or twolaughed and crowed at her motherwas fat and round, and had a thousand little gestures of the angels in Paradise. The child was frightened at the black gipsy woman, and cried; but the mother only kissed her the more, and carried her away, overjoyed at the good fortune the prophetess had foretold to her Agns. She would become a famous beautya wondera queen. So she returned to her garret in the Rue Folle-Peine, proud to bring back with her a queen. The next day she seized a moment when the child was asleep on her bedfor it always slept with herleft the door ajar, and ran to tell a neighbour in the Rue de la Schesserie that the day would come when her daughter Agns would be served at her table by the King of England and the Duke of Ethiopia, and a hundred other surprises. On her return, hearing no sound as she mounted her stair, she said, Good, the child is still asleep. She found the door more open than she had left it; she entered, and ran to the bedpoor mother!the child was gone, the place empty. There was no trace left of the child, excepting one of its little shoes. She fled out of the room and down the stairs and began beating her head against the wall, crying: My child! Who has my child? Who has taken my child from me? The street was empty, the house stood by itself, no one could tell her anything. She hastened through the city, searching every street, running hither and thither the whole day, mad, distraught, terrible to behold, looking in at every door and every window like a wild beast robbed of its young. She was breathless, dishevelled, terrifying, with a flame in her eyes that dried her tears. She stopped the passers-by and cried, My child! my child! my pretty little girl! To him who will restore my child to me I will be a servant, the servant of his dogand he may eat my heart if he will! She met Monsieur the Cur of Saint-Rmy, and to him she said: Monsieur the Cur, I will dig the earth with my nails, but give me back my child! Oudarde, it was heart-rending, and I saw a very hard man, Ma?tre Ponce Lacabre the attorney, shedding tears. Ah, the poor mother! At night she returned to her home. During her absence, a neighbour had seen two Egyptian women steal up her stair with a bundle in their arms, then come down again after closing the door, and hasten away. Afterward she had heard something that sounded like a childs cry from Paquettes room. The mother broke into mad laughter, sprang up the stair as if she had wings, burst open the door like an explosion of artillery, and entered the room. Horrible to relate, Oudarde, instead of her sweet little Agns, so rosy and fresh, a gift from Heaven, a sort of hideous little monster, crippled, one-eyed, all awry, was crawling and whimpering on the floor. She covered her eyes in horror. - Ah! she cried, can these sorceresses have changed my little girl into this frightful beast? They removed the misshapen lump as quickly as possible out of her sight; it would have driven her mad. It was a boy, the monstrous offspring of some Egyptian woman and the Foul Fiend, about four years old, and speaking a language like no human tongue, impossible to understand. La Chantefleurie had thrown herself upon the little shoe, all that remained to her of her hearts delight, and lay so long motionless, without a word or a breath, that we thought she was dead. But suddenly her whole body began to tremble, and she fell to covering her relic with frantic kisses, sobbing the while as if her heart would break. I do assure you, we were all weeping with her as she cried: Oh, my little girl! my pretty little girl! where art thou? It rent the very soul to hear her; I weep now when I think of it. Our children, look you, are the very marrow of our bones.My poor little Eustache, thou too art so beautiful!Could you but know how clever he is! It was but yesterday he said to me, Mother, I want to be a soldier.Oh, my Eustache, what if I were to lose thee!Well, of a sudden, La Chantefleurie sprang to her feet and ran through the streets of the town crying: To the camp of the Egyptians! to the camp of the Egyptians! Sergeants, to burn the witches! The Egyptians were gonedeep night had fallen, and they could not be pursued. Next day, two leagues from Reims, on a heath between Gueux and Tilloy, were found the remains of a great fire, some ribbons that had belonged to Paquettes child, some drops of blood, and goats dung. The night just past had been that of Saturday. Impossible to doubt that the gipsies had kept their Sabbath on this heath, and had devoured the infant in company with Beelzebub, as is the custom among the Mahometans. When La Chantefleurie heard of these horrible things she shed no tear, her lips moved as if to speak, but no words came. On the morrow her hair was gray, and the day after that she had disappeared. -A terrible story indeed, said Oudarde, and one that would draw tears from a Burgundian! -I do not wonder now, added Gervaise, that the fear of the Egyptians should pursue you. -And you were the better advised, said Oudarde, in running away with your Eustache, seeing that these, too, are Egyptians from Poland. -No, said Gervaise, it is said they come from Spain and Catalonia. -Catalonia? Well, that may be, answered Oudarde. Polognia, Catalonia, ValoniaI always confound those three provinces. The sure thing is that theyre Egyptians. -And as sure, added Gervaise, that theyve teeth long enough to eat little children. And I would not be surprised if La Esmeralda did a little of that eating, for all she purses up her mouth so small. That white goat of hers knows too many cunning tricks that there should not be some devilry behind it. -Mahiette pursued her way in silence, sunk in that kind of reverie which is in some sort a prolongation of any pitiful tale, and does not cease till it has spread its emotion, wave upon wave, to the innermost recesses of the heart. -And was it never known what became of La Chantefleurie? asked Gervaise. But Mahiette made no reply till Gervaise, repeating her question, and shaking her by the arm, seemed to awaken her from her musings. -What became of Chantefleurie? said she, mechanically repeating the words just fresh in her ear; then, with an effort, to recall her attention to their sense: Ah, she added quickly, that was never known. -After a pause she went on: Some said they had seen her leave the town in the dusk by the Flchembault gate; others, at the break of day by the old Base gate. A poor man found her gold cross hung upon the stone cross in the field where the fair is held. It was that trinket that had ruined her in 61a gift from the handsome Vicomte de Cormontreuil, her first lover. Paquette would never part with it, even in her greatest povertyshe clung to it as to her life. So, seeing this cross abandoned, we all thought she must be dead. Nevertheless, some people at the Cabaret des Vautes came forward and protested they had seen her pass by on the road to Paris, walking barefoot over the rough stones. But then she must have gone out by the Vesle gate, and that does not agree with the rest. Or rather, I incline to the belief that she did leave by the Vesle gate, but to go out of the world. -I do not understand, said Gervaise. -The Vesle, replied Mahiette with a mournful sigh, is the river. -Alas, poor Chantefleurie! said Oudarde with a shudder, drowned? -Drowned! said Mahiette. And who could have foretold to the good father Guybertaut, when he was passing down the stream under the Tinqueux bridge, singing in his boat, that one day his dear little Paquette should pass under that same bridge, but without either boat or song! -And the little shoe? asked Gervaise. -Vanished with the mother. -Poor little shoe! sighed Oudarde; fat, tender-hearted creature, she would have been very well pleased to go on sighing in company with Mahiette; but Gervaise, of a more inquiring disposition, was not at an end of her questions. -And the little monster? she suddenly said to Mahiette. -What monster? -The little gipsy monster left by the black witches in the place of Chantefleuries little girl. What was done with it? I trust you had it drowned? -No, answered Mahiette, we did not. -What? burned, then? I faith, a better way for a witchs spawn! -Neither drowned nor burned, Gervaise. His Lordship the archbishop took pity on the child of Egypt, exorcised it, blessed it, carefully cast the devil out of its body, and then sent it to Paris to be exposed as a foundling on the wooden bed in front of Notre-Dame. -Ah, these bishops, grumbled Gervaise; because they are learned, forsooth, they can never do anything like other folks! Think of it, Oudardeto put the devil among the foundlings! for of course the little monster was the devil. Well, Mahiette, and what did they do with him in Paris? Ill answer for it that no charitable person would have it. -I know not, answered the lady of Reims. It was just at the time when my husband purchased the office of clerk to the Court of Justice at Beru, two leagues distant from the city, and we thought no further of the story, particularly that just in front of Beru are the two little hills of Cernay, which hide the towers of the Cathedral from view. -Meanwhile, the three worthy burgher wives had reached the Place de Grve. Absorbed in conversation, however, they had passed the public breviary of the Tour-Roland without noticing it, and were directing their steps mechanically towards the pillory round which the crowd increased from moment to moment. It is possible that the sight which at that instant drew all eyes towards it would have completely driven the Rat-Hole and the pious halt they intended making there from their minds, had not fat, six-year-old Eustache, dragging at Mahiettes side, recalled it to them suddenly. -Mother, said he, as if some instinct apprised him that they had left the Rat-Hole behind, now may I eat the cake? -Had Eustache been more astute, that is to say, less greedy, he would have waited, and not till they had returned to the University, to Maitre Andry Musniers house in the Rue Madame-la-Valence, and he had put the two arms of the Seine and the five bridges of the city between the Rat-Hole and the cake, would he have hazarded this question. -Imprudent though the question was on Eustaches part, it recalled his mother to her charitable purpose. -That reminds me, exclaimed she, we were forgetting the nun! Show me this Rat-Hole of yours, that I may give her the cake. -Right gladly, said Oudarde; it will be a charity. -This was quite out of Eustaches reckoning. -Its my cake! said he, drawing up first one shoulder and then the other till they touched his earsa sign, in such cases, of supreme dissatisfaction. -The three women retraced their steps and presently reached the Tour-Roland. -Said Oudarde to the other two: We must not all look into the cell at once, lest we frighten the recluse. Do you two make as if you were reading Dominus in the breviary, while I peep in at the window. The sachette knows me somewhat. I will give you a sign when you may come. -Accordingly, she went alone to the window. As her gaze penetrated the dim interior, profound pity overspread her countenance, and her frank and wholesome face changed as suddenly in expression and hue as if it had passed out of the sunshine into moonlight. Her eyes moistened and her lips contracted as before an outbreak of tears. The next moment she laid her finger on her lips and signed to Mahiette to come and look. -Mahiette advanced, tremulous, silent, on tip-toe, as one approaching a death-bed. -It was, in truth, a sorrowful spectacle which presented itself to the eyes of the two women, as they gazed, motionless and breathless, through the barred aperture of the Rat-Hole. -The cell was small, wider than it was deep, with a vaulted, Gothic ceiling, giving it much the aspect of the inside of a bishops mitre. Upon the bare flag-stones which formed its floor, in a corner a woman was seated, or rather crouching, her chin resting on her knees, which her tightly clasped arms pressed close against her breast. Cowering together thus, clothed in a brown sack which enveloped her entirely in its large folds, her long, gray hair thrown forward and falling over her face along her sides and down to her feet, she seemed, at the first glance, but a shapeless heap against the gloomy background of the cell, a dark triangle which the daylight struggling through the window divided sharply into two halves, one light, the other darkone of those spectres, half light, half shade, such as one sees in dreams, or in one of Goyas extraordinary workspale, motionless, sinister, crouching on a tomb or leaning against the bars of a prison. You could not say definitely that it was a woman, a man, a living being of any sort; it was a figure, a vision in which the real and the imaginary were interwoven like light and shadow. Beneath the hair that fell all about it to the ground, you could just distinguish the severe outline of an emaciated face, just catch a glimpse under the edge of the garment of the extremity of a bare foot, clinging cramped and rigid to the frozen stones. The little of human form discernible under that penitential covering sent a shudder through the beholder. -This figure, which might have been permanently fixed to the stone floor, seemed wholly without motion, thought, or breath. In that thin covering of sackcloth, in January, lying on the bare stones, without a fire, in the shadow of a cell whose oblique loophole admitted only the northeast wind, but never the sunshine, she seemed not to suffer, not even to feel. You would have thought she had turned to stone with the dungeon, to ice with the season. Her hands were clasped, her eyes fixed; at the first glance you took her for a spectre; at the second, for a statue. -However, at intervals, her livid lips parted with a breath and quivered, but the movement was as dead and mechanical as leaves separated by the breeze; while from those dull eyes came a look, ineffable, deep, grief-stricken, unwavering, immutably fixed on a corner of the cell which was not visible from without; a gaze which seemed to concentrate all the gloomy thoughts of that agonized soul upon some mysterious object. -Such was the being who, from her habitation, was called the recluse, and from her sackcloth garment, the sachette. -The three womenfor Gervaise had joined Mahiette and Oudardelooked through the window, and though their heads intercepted the feeble light of the cell, its miserable tenant seemed unaware of their scrutiny. -Let us not disturb her, whispered Oudarde; she is in one of her ecstasies, she is praying. -Meanwhile Mahiette gazed in ever-increasing earnestness upon that wan and withered face and that dishevelled head, and her eyes filled with tears. That would indeed be strange! she murmured. -She pushed her head through the cross-bars of the window, and succeeded in obtaining a glimpse into that corner of the cell upon which the unfortunate womans eyes were immovably fixed. When she withdrew her head, her face was bathed in tears. -What do you call that woman? she asked of Oudarde. -We call her Sister Gudule, was the reply. -And I, said Mahiette, I call her Paquette la Chantefleurie! -Then, with her finger on her lips, she signed to the amazed Oudarde to look through the bars of the window in her turn. Oudarde did so, and saw in that corner, upon which the eye of the recluse was fixed in gloomy trance, a little shoe of rose-coloured satin covered with gold and silver spangles. Gervaise took her turn after Oudarde, after which the three women gazing upon the unhappy mother mingled their tears of distress and compassion. -But neither their scrutiny nor their weeping had stirred the recluse. Her hands remained tightly locked, her lips silent, her eyes fixed, and to any one who knew her story that little shoe thus gazed at was a heart-breaking sight. -None of the three women had uttered a word; they dared not speak, not even in a whisper. This deep silence, this profound grief, this abstraction, in which all things were forgotten save that one, affected them like the sight of the High Altar at Easter or at Christmastide. A sense of being in some holy place came upon them; they were ready to fall on their knees. -At length Gervaise, the most inquiring of the three, and therefore the least sensitive, endeavoured to get speech of the recluse. Sister Gudule! Sister! she called repeatedly, raising her voice louder each time. -The recluse never stirred. Not a word, not a glance, not a breath, not a sign of life. -Oudarde, in a softer and more caressing tone, tried in her turn. Sister! she called; Sister Gudule! -The same silence, the same immobility. -A strange woman indeed! cried Gervaise; no bombard would make her move. -Perhaps she is deaf, suggested Oudarde. -Or blind, added Gervaise. -Perhaps she is dead, said Mahiette. In truth, if the soul had not actually quitted that inert, motionless, lethargic body, at least it had withdrawn itself to such inaccessible depths that the perceptions of the external organs were powerless to reach it. -There remains nothing for us to do, then, said Oudarde, but to leave the cake on the ledge of the window. But then, some boy will be sure to take it away. What can we do to arouse her? -Eustache, whose attention up till now had been distracted by the passing of a little cart drawn by a great dog, now noticed that his three companions were looking at something through the window above him, and, seized in his turn with curiosity, he mounted upon a stone, stood on tip-toe, and stretched up his round, rosy face to the hole, crying, Mother, let me see too! -At the sound of the childs clear, fresh, ringing voice the recluse started violently. She turned her head with the sharp and sudden motion of a steel spring, the two long, fleshless hands drew aside the veil of hair from her brow, and she fixed upon the child a pair of bewildered and despairing eyes. -It was but a glance. Oh, my God! she cried, suddenly burying her face in her knees, and it seemed as if her hoarse voice tore her breast in passing, in pity do not Show me those of others! -Good-morrow, dame, said the child soberly. -The shock had awakened the recluse from her trance. A long shiver ran through her from head to foot, her teeth chattered, she half raised her head, and pressing her arms to her sides, she took her feet in her hands as if to warm them. -Oh, the bitter cold! she murmured. -Poor soul! said Oudarde in deepest pity, will you have a little fire? -She shook her head in token of refusal. -Then, Oudarde went on, holding out a flask to her, here is hippocras; that will warm you -- drink. -She shook her head again and looked fixedly at Oudarde. Water, she said. -No, sister, Oudarde insisted, that is no drink for a January day. You must have a little hippocras, and eat this wheaten cake we have baked for you. -She pushed away the cake Mahiette held out to her, and said, Some black bread. -Come, said Gervaise, seized with charity in her turn, and taking off her woollen cloak, here is a cloak something warmer than yours. Put it round your shoulders. -But she refused this as she had done the flask and the cake. A sack, she answered. -But you must have something to show that yesterday was a holiday! urged the good Oudarde. -I know it well, answered the recluse; these two days I have had no water in my pitcher. -After a moments silence she continued, It is a holiday, so they forget me. They do well. Why should the world think of me, who think not of it? Cold ashes to a dead brand! -And as if exhausted by having said thus much, she let her head fall again upon her knees. The simple-minded, compassionate Oudarde gathering from these last words that the poor woman was still lamenting at the cold, said once more: -Then will you not have some fire? -Fire! answered the woman in a strange tone, and will you make a fire for the poor little one that has been under the ground these fifteen years? -She trembled in every limb, her voice shook, her eyes gleamed, she had risen to her knees. Suddenly she stretched out a thin and bloodless hand and pointed to the child, who gazed at her round-eyed and wondering. Take away that child, she cried, the Egyptian is coming by! -Then she fell on her face on the ground, her forehead striking the floor with the sound of stone upon stone. The three women thought her dead; but a moment afterward she stirred, and they saw her drag herself on her hands and knees to the corner where the little shoe lay. At this they dared look no longer; they saw her not, but they heard the sound of a tempest of sighs and kisses, mingled with heart-rending cries and dull blows as of a head being struck against a wall; then, after one of these blows, so violent that they all three recoiled in horror, deep silence. -Can she have killed herself? asked Gervaise, venturing her head through the bars. Sister! Sister Gudule! -Sister Gudule! echoed Oudarde. -Alas, she does not move! cried Gervaise; can she be dead? Gudule! Gudule! -Mahiette, whom deep emotion had rendered speechless, now made an effort. Wait a moment, said she; then going close to the windowPaquette! she criedPaquette la Chantefleurie! -A child blowing unsuspiciously on the half-lighted match of a petard, causing it suddenly to explode in his face, would not be more appalled than Mahiette at the effect of this name, thus unexpectedly launched into Sister Gudules cell. -The recluse shook in every limb, then, rising to her feet, she sprang at the loophole with eyes so blazing that the three women and the child all fell back to the very edge of the quay. -Meanwhile the terrible face of the recluse remained close to the grating. Oh! oh! she cried, with a horrible laugh, it is the Egyptian woman calling me! -At that moment a scene which was taking place on the pillory caught her haggard eye. Her brow contracted with horror, she stretched her two skeleton arms through the cross-bars, and cried in a voice like the rattle in a dying throat, Tis thou again, daughter of Egypt! Tis thou calling me, stealer of children! Accursed be thou foreveraccursed! accursed! accursed! -Chapter 4 - A Tear for a Drop of Water -The concluding words of the foregoing chapter may be described as the point of junction of two scenes which, till that moment, had been running parallel, each on its own particular stage; the onewhich we have just been followingat the Rat-Hole; the othernow to be describedon the pillory. The former had been witnessed only by the three women with whom the reader has just been made acquainted; the latter had for audience the whole crowd which we saw gathering in the Place de Grve round the pillory and the gibbet. -This crowd, in whom the sight of the four sergeants stationed since nine in the morning at the four corners of the pillory had roused the pleasing expectation of a penal exhibition of some sortnot, perhaps, a hanging, but a flogging, a cutting off of ears or the likethis crowd had increased so rapidly that the four mounted men, finding themselves too closely pressed, had more than once been under the necessity of tightening it, as they called it then, by great lashes of their whips and their horses heels. -The populace, well accustomed to waiting for public executions, manifested but little impatience. They amused themselves by looking at the pillory, a very simple structure, consisting of a hollow cube of masonry some ten feet in height. A steep flight of steps of unhewn stonecalled par excellence the ladderled to the top platform, on which lay horizontally a wheel of stout oak. To this wheel the victim was bound kneeling and with his hands pinioned behind him; a shaft of timber, set in motion by a windlass concealed in the interior of the structure, caused the wheel to rotate horizontally, thus presenting the face of the culprit to every point of the Place in succession. This was called turning the criminal. -It will be seen from the description that the pillory of the Grve was far from possessing the many attractions of that at the Halles. Here was nothing architectural, nothing monumentalno roof embellished with an iron cross, no octagon lantern tower, no slender pilasters blossoming out against the edge of the roof into acanthus-leafed and flowery capitals, no fantastic, dragon-headed gargoyles, no carved wood-work, no delicate sculpture cut deeply into the stone. -One had to be content with the four rough-hewn sides of stone and an ugly stone gibbet, mean and bare, at the side of it. The show would have been a poor one to the amateur of Gothic architecture, but truly nobody could be more indifferent in the matter of architecture than the good burghers of the Middle Ages; they cared not a jot for the beauty of a pillory. -At last the culprit arrived, tied to a carts tail, and as soon as he was hoisted on to the platform and, bound with cords and straps to the wheel, was plainly visible from every point of the Place, a prodigious hooting mingled with laughter and acclamations burst from the assembled crowd. They had recognised Quasimodo. -It was indeed he. Strange turn of fortunes wheel!to be pilloried on the same spot on which, but the day before, he had been saluted, acclaimed Pope and Prince of Fools, and counted in his train the Duke of Egypt, the King of Tunis, and the Emperor of Galilee. One thing, however, is certain, there was no mind in that crowd, not even his own, though in turn the victor and the vanquished, that thought of drawing this parallel. Gringoire and his philosophy were lacking at this spectacle. -Presently Michel Noiret, appointed trumpeter to our lord the King, after imposing silence on the people, made proclamation of the sentence, pursuant to the ordinance and command of the Lord Provost. He then fell back behind the cart with his men. -Quasimodo, quite impassive, never stirred a muscle. All resistance was impossible to him by reason of what, in the parlance of the old criminal law, was described as the strength and firmness of the bondsin other words, the cords and chains probably cut into his flesh. This tradition of the dungeon and the galleys has been handed down to us and carefully preserved among us civilized, tender-hearted, humane people in the shape of the manaclesnot forgetting the bagnio and the guillotine, of course. -Quasimodo had passively let himself be led, thrust, carried, hoisted up, bound and rebound. Nothing was to be discovered in his face but the bewilderment of the savage or the idiot. He was known to be deaf; he might also have been blind. -They thrust him on to his knees on the wheel, they stripped him to the waist; he made no resistance. They bound him down with a fresh arrangement of cords and leathern thongs; he let them bind and strap him. Only from time to time he breathed heavily, like a calf whose head swings and bumps over the edge of a butchers cart. -The blockhead, said Jehan Frollo of the Mill to his friend Robin Poussepain (for the two scholars had followed the culprit, as in duty bound), he knows no more what its all about than a bumble-bee shut in a box! -There was a great burst of laughter from the crowd when, stripped naked to their view, they caught sight of Quasimodos hump, his camels breast, his brawny, hairy shoulders. During the merriment a man in the livery of the Town, short of stature and of burly make, ascended to the platform and stationed himself beside the culprit. His name was quickly circulated among the spectators. It was Master Pierrat Torterue, official torturer to the Chatelet. -He first proceeded to deposit on a corner of the pillory a black hour-glass, the upper cup of which was filled with red sand, which ran into the lower receptacle; he then divested himself of his party-coloured doublet, and dangling from his right hand there appeared a scourge with long, slender, white thongsshining, knotted, interlacedand armed with metal claws. With his left hand he carelessly drew the shirt-sleeve up his right arm as high as the shoulder. -At this Jehan Frollo, lifting his curly, fair head above the crowd (for which purpose he had mounted on the shoulders of Robin Poussepain), shouted: Walk up, walk up, ladies and gentlemen, and see them scourge Ma?tre Quasimodo, bell-ringer to my brother the reverend archdeacon of Josas, a rare specimen of Oriental architecture, with a domed back, and twisted columns for legs! -And the crowd roared again, especially the young people. -The torturer now stamped his foot; the wheel began to move. Quasimodo swayed under his bonds, and the amazement suddenly depicted on that misshapen countenance gave a fresh impulse to the peals of laughter round about. -Suddenly, at the moment when the wheel in its rotation presented to Master Pierrat Quasimodos enormous back, the torturer raised his arm, the thongs hissed shrilly through the air, like a handful of vipers, and fell with fury on the shoulders of the hapless wretch. -Quasimodo recoiled as if suddenly startled out of sleep. Now he began to understand. He writhed in his bonds, the muscles of his face contracted violently in surprise and pain, but not a sound escaped him. He only rolled his head from side to side, like a bull stung in the flank by a gadfly. -A second stroke followed the first, then a third, and another, and another. The wheel ceased not to turn, nor the lashes to rain down. Soon the blood began to flow; it trickled in a thousand streams over the dark shoulders of the hunchback, and the keen thongs, as they swung round in the air, scattered it in showers over the multitude. -Quasimodo had resumed, in appearance at least, his former impassibility. At first he had striven, silently and without any great external movement, to burst his bonds. His eye kindled, his muscles contracted, his limbs gathered themselves up. The effort was powerful, strenuous, desperate, and the cords and straps were strained to their utmost tension; but the seasoned bonds of the provostry held. They cracked, but that was all. Quasimodo desisted, exhausted by the effort, and the stupefaction on his face was succeeded by an expression of bitter and hopeless discouragement. He closed his single eye, dropped his head upon his breast, and gave no further sign of life. -Thenceforward he did not stir; nothing could wring a movement from himneither the blood, that did not cease to flow, nor the strokes which fell with redoubled fury, nor the violence of the torturer, who had worked himself into a state of frenzy, nor the shrill and strident whistle of the scourge. -At length an usher of the Chatelet, clad in black, mounted on a black horse, and stationed at the foot of the ladder since the beginning of the chastisement, pointed with his ebony staff to the hour-glass. The torturer held his hand, the wheel stopped. Quasimodo slowly reopened his eye. -The scourging was over. Two assistants of the torturer bathed the lacerated shoulders of the culprit, applied to them some kind of unguent which immediately closed the wounds, and threw over his back a yellow cloth shaped like a chasuble; Pierrat Torterue meanwhile letting the blood drain from the lashes of his scourge in great drops on to the ground. -But all was not yet over for poor Quasimodo. He had still to undergo that hour on the pillory which Ma?tre Florian Barbedienne had so judiciously added to the sentence of Messire Robert dEstouteville; and all merely to prove the truth of John of Cumeness ancient physiological and psychological jeu de mots: Surdus absurdus. -They accordingly turned the hour-glass, and left the hunch-back bound to the wheel, that justice might run its course to the end. -The peopleparticularly in the Middle Agesare to society what the child is in the family; and as long as they are allowed to remain at that primitive stage of ignorance, of moral and intellectual nonage, it may be said of them as of childhoodIt is an age that knows not pity. -We have already shown that Quasimodo was universally hatedfor more than one good reason, it must be admittedfor there was hardly an individual among the crowd of spectators but had or thought he had some cause of complaint against the malevolent hunchback of Notre-Dame. All had rejoiced to see him make his appearance on the pillory; and the severe punishment he had just undergone, and the pitiable plight in which it had left him, so far from softening the hearts of the populace, had rendered their hatred more malicious by pointing it with the sting of merriment. -Accordingly, public vengeancevindicte publique, as the jargon of the law courts still has itbeing satisfied, a thousand private revenges now had their turn. Here, as in the great Hall, the women were most in evidence. Every one of them had some grudge against himsome for his wicked deeds, others for his ugly faceand the latter were the most incensed of the two. -Oh, image of the Antichrist! cried one. -Thou rider on the broomstick! screamed another. -Oh, the fine tragical grimace! yelled a third, and that would have made him Pope of Fools if to-day had been yesterday. -Good! chimed in an old woman, this is the pillory grin. When are we going to see him grin through a noose? -When shall we see thee bonneted by thy great bell and driven a hundred feet underground, thrice-cursed bell-ringer? -And to think that this foul fiend should ring the Angelus! -Oh, the misbegotten hunchback! the monster! -To look at him is enough to make a woman miscarry better than any medicines or pharmacy. -And the two scholars, Jehan of the Mill and Robin Poussepain, struck in at the pitch of their voices with the refrain of an old popular song: -A halter -For the gallows rogue, -A fagot -For the witchs brat. -A thousand abusive epithets were hurled at him, with hoots and imprecations and bursts of laughter, and now and then a stone or two. -Quasimodo was deaf, but he saw very clearly, and the fury of the populace was not less forcibly expressed in their faces than in their words. Besides, the stones that struck him explained the bursts of laughter. -At first he bore it well enough. But, by degrees, the patience that had remained inflexible under the scourge of the torturer relaxed and broke down under the insect stings. The Asturian bull that bears unmoved the attack of the picadors is exasperated by the dogs and banderillas. -Slowly he cast a look of menace over the crowd; but, bound hand and foot as he was, his glance was impotent to drive away these flies that stung his wounds. He shook himself in his toils, and his furious struggles made the old wheel of the pillory creak upon its timbers; all of which merely served to increase the hooting and derision. -Then the poor wretch, finding himself unable to burst his fetters, became quiet again; only at intervals a sigh of rage burst from his tortured breast. No flush of shame dyed that face. He was too far removed from social convention, too near a state of nature, to know what shame was. In any case, at that degree of deformity, is a sense of infamy possible? But resentment, hatred, and despair slowly spread a cloud over that hideous countenance, growing ever more gloomy, ever more charged with electricity, which flashed in a thousand lightnings from the eye of the Cyclops. -Nevertheless, the cloud lifted a moment, at the appearance of a mule which passed through the crowd, ridden by a priest. From the moment that he caught sight of the priest, the poor victims countenance softened, and the rage that distorted it gave place to a strange soft smile full of ineffable tenderness. -As the priest approached nearer, this smile deepened, became more distinct, more radiant, as though the poor creature hailed the advent of a saviour. Alas! no sooner was the mule come near enough to the pillory for its rider to recognise the person of the culprit, than the priest cast down his eyes, turned his steed abruptly, and hastened away, as if anxious to escape any humiliating appeal, and not desirous of being recognised and greeted by a poor devil in such a position. -This priest was the Archdeacon Dom Claude Frollo. -And now the cloud fell thicker and darker than before over the face of Quasimodo. The smile still lingered for a while, but it was bitter, disheartened, unutterably sad. -Time was passing: he had been there for at least an hour and a half, lacerated, abused, mocked, and well-nigh stoned to death. -Suddenly he renewed his struggles against his bonds with such desperation that the old structure on which he was fixed rocked beneath him. Then, breaking the silence he had obstinately preserved, he cried aloud in a hoarse and furious voice, more like the cry of a dog than a human being, and that rang above the hooting and the shouts, Water! -This cry of distress, far from moving them to compassion, only added to the amusement of the populace gathered round the pillory, who, it must be admitted, taking them in a mass, were scarcely less cruel and brutal than that debased tribe of vagabonds whom we have already introduced to the reader. Not a voice was raised around the unhappy victim save in mockery of his thirst. Undoubtedly his appearance at that momentwith his purple, streaming face, his eye bloodshot and distraught, the foam of rage and pain upon his lips, his lolling tonguemade him an object rather of repulsion than of pity; but we are bound to say that had there even been among the crowd some kind, charitable soul tempted to carry a cup of water to that poor wretch in agony, there hung round the steps of the pillory, in the prejudice of the times, an atmosphere of infamy and shame dire enough to have repelled the Good Samaritan himself. -At the end of a minute or two Quasimodo cast his despairing glance over the crowd once more, and cried in yet more heart-rending tones, Water! -Renewed laughter on all sides. -Drink that! cried Robin Poussepain, throwing in his face a sponge soaked in the kennel. Deaf rogue, I am thy debtor. -A woman launched a stone at his headThat shall teach thee to wake us at night with thy accursed ringing. -Ah-ha, my lad, bawled a cripple, trying to reach him with his crutch, wilt thou cast spells on us again from the towers of Notre-Dame, I wonder? -Heres a porringer to drink out of, said a man, hurling a broken pitcher at his breast. Tis thou, that only by passing before her, caused my wife to be brought to bed of a child with two heads! -And my cat of a kit with six legs! screamed an old woman as she flung a tile at him. -Water! gasped Quasimodo for the third time. -At that moment he saw the crowd part and a young girl in fantastic dress issue from it; she was accompanied by a little white goat with gilded horns, and carried a tambourine in her hand. -Quasimodos eye flashed. It was the gipsy girl he had attempted to carry off the night before, for which piece of daring he felt in some confused way he was being chastised at that very moment; which was not in the least the case, seeing that he was punished only for the misfortune of being deaf and having had a deaf judge. However, he doubted not that she, too, had come to have her revenge and to aim a blow at him like the rest. -He beheld her rapidly ascend the steps. Rage and vexation choked him; he would have burst the pillory in fragments if he could, and if the flash of his eye had possessed the lightnings power, the gipsy would have been reduced to ashes before ever she reached the platform. -Without a word she approached the culprit, who struggled vainly to escape her, and detaching a gourd bottle from her girdle, she raised it gently to those poor parched lips. -Then from that eye, hitherto so dry and burning, there rolled a great tear which trickled down the uncouth face, so long distorted by despair and painthe first, maybe, the hapless creature had ever shed. -But he had forgotten to drink. The gipsy impatiently made her little familiar grimace; then, smiling, held the neck of the gourd to Quasimodos tusked mouth. -He drank in long draughts; he was consumed with thirst. -When, at last, he had finished, the poor wretch advanced his black lipsno doubt to kiss the fair hands which had just brought him relief; but the girl, mistrusting him perhaps, and remembering the violent attempt of the night before, drew back her hand with the frightened gesture of a child expecting to be bitten by some animal. Whereat the poor, deaf creature fixed upon her a look full of reproach and sadness. -In any place it would have been a touching spectacle to see a beautiful girlso fresh, so pure, so kind, and so unprotectedhastening thus to succour so much of misery, of deformity and wickedness. On a pillory, it became sublime. -The people themselves were overcome by it, and clapped their hands, shouting, No?l! No?l! -It was at this moment that the recluse, through the loophole of her cell, caught sight of the gipsy girl on the steps of the pillory, and launched her sinister imprecation: Cursed be thou, daughter of Egypt! cursed! cursed! -Chapter 5 - The end of the Wheaten Cake -Esmeralda blanched and swayed as she descended the steps of the pillory, the voice of the recluse pursuing her as she went: Come down! come down! Ah, thou Egyptian thief, thou shalt yet return there again! -The sachette is in one of her tantrums, murmured the people; but they went no further, for these women were feared, which made them sacred. In those days they were shy of attacking a person who prayed day and night. -The hour had now arrived for releasing Quasimodo. They unfastened him from the pillory, and the crowd dispersed. -Near the Grand-Pont, Mahiette, who was going away with her companions, suddenly stopped. Eustache, she said, what hast thou done with the cake? -Mother, answered the child, while you were talking to the dame in the hole a great dog came and took a bite of my cake, and so then I too had a bite. -What, sir, she cried, you have eaten it all! -Mother, it was the dog. I told him, but he would not listen; then I bit a piece too. - Tis a shocking boy! said the mother, smiling fondly while she scolded. Look you, Oudarde, already he eats by himself all the cherries in our little orchard at Charlerange. So his grandfather predicts he will be a captain.Let me catch you at it again, Monsieur Eustache. Go, greedy lion! -BOOK VII -Chapter 1 - Showing the danger of Confiding ones Secret to a Goat -Several weeks had elapsed. -It was the beginning of March, and though Du Bartas,1 that classic ancestor of the periphrase, had not yet styled the sun the Grand Duke of the Candles, his rays were none the less bright and cheerful. It was one of those beautiful mild days of early spring that draw all Paris out into the squares and promenades as if were a Sunday. On these days of clear air, of warmth, and of serenity there is one hour in particular at which the great door of Notre-Dame is seen at its best. That is at the moment when the sun, already declining in the west, stands almost directly opposite the front of the Cathedral; when his rays, becoming more and more horizontal, slowly retreat from the flag-stones of the Place and creep up the sheer face of the building, making its innumerable embossments stand forth from the shadow, while the great central rose-window flames like a Cyclopss eye lit up by the glow of a forge. -It was at this hour. -Opposite to the lofty Cathedral, now reddened by the setting sun, on the stone balcony over the porch of a handsome Gothic house at the corner formed by the Place and the Rue du Parvis, a group of fair damsels were laughing and talking with a great display of pretty airs and graces. By the length of the veils which fell from the tip of their pearl-encircled pointed coif down to their heels; by the delicacy of the embroidered chemisette which covered the shoulders but permitted a glimpseaccording to the engaging fashion of the dayof the swell of the fair young bosom; by the richness of their under-petticoats, more costly than the overdress (exquisite refinement); by the gauze, the silk, the velvet stuffs, and, above all, by the whiteness of their hands, which proclaimed them idle and unemployed, it was easy to divine that they came of noble and wealthy families. They were, in effect, the Damoiselle Fleur-de-Lys de Gondelaurier and her companions, Diane de Christeuil, Amelotte de Montmichel, Colombe de Gaillefontaine, and the little De Champchevrierall daughters of good family, gathered together at this moment in the house of the widowed Mme. Alo?se de Gondelaurier, on account of Monseigneur the Lord of Beaujeu and Madame Anne, his wife, who were coming to Paris in April in order to choose the maids-in-waiting for the Dauphiness Margaret when they went to Picardy to receive her from the hands of the Flemings. So all the little landed proprietors for thirty leagues round were eager to procure this honour for their daughters, and many of them had already brought or sent them to Paris. The above-mentioned maidens had been confided by their parents to the discreet and unimpeachable care of Mme. Alo?se de Gondelaurier, the widow of a captain of the Kings archers, and now living in elegant retirement with her only daughter in her mansion in the Place du Parvis, Notre-Dame, at Paris. -The balcony on which the girls were seated opened out of a room richly hung with tawny-coloured Flanders leather stamped with gold foliage. The beams that ran in parallel lines across the ceiling charmed the eye by their thousand fantastic carvings, painted and gilt. Gorgeous enamels gleamed here and there from the doors of inlaid cabinets; a wild boars head in faience crowned a magnificent side-board, the two steps of which proclaimed the mistress of the house to be the wife or widow of a knight banneret. At the further end of the room, in a rich red velvet chair, beside a lofty chimney-piece, blazoned from top to bottom with coats of arms, sat Mme. de Gondelaurier, whose five-and-fifty years were no less distinctly written on her dress than on her face. -Beside her stood a young man whose native air of breeding was somewhat heavily tinged with vanity and bravadoone of those handsome fellows whom all women are agreed in adoring, let wiseacres and physiognomists shake their heads as they will. This young cavalier wore the brilliant uniform of a captain of the Kings archers, which too closely resembles the costume of Jupiter, which the reader has had an opportunity of admiring at the beginning of this history, for us to inflict on him a second description. -The damoiselles were seated, some just inside the room, some on the balcony, on cushions of Utrecht velvet with gold corners, or on elaborately carved oak stools. Each of them held on her knees part of a great piece of needlework on which they were all engaged, while a long end of it lay spread over the matting which covered the floor. -They were talking among themselves with those whispers and stifled bursts of laughter which are the sure signs of a young mans presence among a party of girls. The young man himself who set all these feminite wiles in motion, appeared but little impressed thereby, and while the pretty creatures vied with one another in their endeavours to attract his attention, he was chiefly occupied in polishing the buckle of his sword-belt with his doeskin glove. -From time to time the old lady addressed him in a low voice, and he answered as well as might be with a sort of awkward and constrained politeness. From the smiles and significant gestures of Madame Alo?se, and the meaning glances she threw at her daughter, Fleur-de-Lys, as she talked to the captain, it was evident that the conversation turned on some betrothal already accomplished or a marriage in the near future between the young man and the daughter of the house. Also, from the cold and embarrassed air of the officer, it was plainly to be seen than, as far as he was concerned, there was no longer any question of love. His whole demeanour expressed a degree of constraint and ennui such as a modern subaltern would translate in the admirable language of to-day by, What a beastly bore! -The good lady, infatuated like many another mother with her daughter, never noticed the officers lack of enthusiasm; but gave herself infinite pains to call his attention in a whisper to the matchless grace with which Fleur-de-Lys used her needle or unwound her silk thread. -Look, little cousin, said she, pulling him by the sleeve and speaking into his ear, look at her nownow, as she bends. -Quite so, replied the young man; and he fell back into his former icy and abstracted silence. -The next moment he had to lean down again to Madame Alo?se. Have you ever, said she, seen a blither and more engaging creature than your intended? She is all lily-white and golden. Those hands, how perfect and accomplished! and that neck, has it not all the ravishing curves of a swans? How I envy you at times! and how fortunate you are in being a man, naughty rake that you are! Is not my Fleur-de-Lys beautiful to adoration, and you head over ears in love with her? -Assuredly, he replied, thinking of something else. -Speak to her, then, said Madame Alo?se, pushing him by the shoulder. Go and say something to her; you have grown strangely timid. -We can assure our readers that timidity was no virtue or fault of the captain. He made an effort, however, to do as he was bid. -Fair cousin, said he, approaching Fleur-de-Lys, what is the subject of this piece of tapestry you are working at? -Fair cousin, answered Fleur-de-Lys somewhat pettishly, I have already informed you three times. It is the grotto of Neptune. -It was evident that Fleur-de-Lys saw more plainly than her mother through the cold and absent manner of the captain. He felt the necessity of pursuing the conversation further. -And who is to benefit by all this fine Neptunery?he asked. -It is for the Abbey of Saint-Antoine-des-Champs, answered Fleur-de-Lys, without raising her eyes. -The captain picked up a corner of the tapestry. And pray, fair cousin, who may be this big, puffy-cheeked gendarme blowing a trumpet? -That is Triton, she replied. -There was still a touch of resentment in the tone of these brief answers, and the young man understood perfectly that it behooved him to whisper in her ear some pretty nothing, some stereotyped gallantryno matter what. He bent over her accordingly, but his imagination could furnish nothing more tender or personal than: Why does your mother always wear a gown emblazoned with her heraldic device, as our grandmothers did in the time of Charles VII? Prithee, fair cousin, tell her that is no longer the fashion of the day, and that these hinges and laurel-trees embroidered on her gown make her appear like a walking mantel-piece. Nobody sits on their banner like that nowadays, I do assure you! -Fleur-de-Lys raised her fine eyes to him reproachfully. And is that all you have to assure me of? she asked in low tones. -Meanwhile the good Dame Alo?se, overjoyed to see them thus leaning together and whispering, exclaimed as she trifled with the clasps of her book of hours: Touching scene of love! -The captain, more and more embarrassed, returned helplessly to the subject of the tapestry. I faith, a charming piece of work! he exclaimed. -At this juncture Colombe de Gaillefontaine, another pink-and-white, golden-haired beauty, dressed in pale blue damask, ventured a shy remark to Fleur-de-Lys, hoping however that the handsome soldier would answer her. -Dear Gondelaurier, have you seen the tapestries at the H?tel de la Roche-Guyon? -Is not that where there is a garden belonging to the Linenkeeper of the Louvre? asked Diane de Christeuil with a laugh; for having beautiful teeth she laughed on all occasions. -And where there is a great ancient tower, part of the old wall of Paris? added Amelotte de Montmichel, a charming, curly-haired, who had a trick of sighing, just as Diane laughed, without any valid reason. -My dear Colombe, said Dame Alo?se, do you mean the H?tel which belonged to M. de Bacqueville in the reign of King Charles VI? There are, in effect, some superb high-warp tapestries there. -Charles VIKing Charles VI! muttered the young officer, twirling his mustache. Heavens! how far back does the old ladys memory reach? -Superb tapestries! repeated Mme. de Gondelaurier. So much so, indeed, that they are accounted absolutely unique. -At this moment Berangre de Champchevrier, a slip of a little girl of seven, who had been looking down into the Place through the carved trefoils of the balcony, cried out: Oh, godmother Fleur-de-Lys, do look at this pretty girl dancing and playing the tambourine in the street in the middle of that ring of people! -The penetrating rattle of a tambourine rose up to them from the square. -Some gipsy of Bohemia, said Fleur-de-Lys, turning her head carelessly towards the square. -Let us looklet us look! cried her companions, eagerly running to the balustrade, while she followed more slowly, musing on the coldness of her betrothed. The latter, thankful for this incident, which cut short an embarrassing conversation, returned to the other end of the apartment with the well-contented air of a soldier relieved from duty. -Yet it was an easy and pleasant service, that of being on duty at the side of the fair Fleur-de-Lys, and time was when he had thought it so. But the captain had gradually wearied of it, and the thought of his approaching marriage grew more distasteful to him every day. Moreover, he was of inconstant disposition, and, we are bound to confess, of somewhat vulgar proclivities. Although of very noble birth, he had with his uniform adopted many of the low habits of the common soldier. The tavern and all that belongs to it delighted him; and he was never at his ease but amid gross language, military gallantries, facile beauties, and easy conquests. Nevertheless, he had received from his family a certain amount of education and polish, but he had too early been allowed to run loose, had been thrust too young into garrison life, and the varnish of polite manner had not been sufficiently thick to withstand the constant friction of the soldiers harness. Though still visiting her occasionally, from some last remnant of kindly feeling, he felt himself increasingly constrained in the presence of Fleur-de-Lys; partly because by dint of dividing his love so freely on all sides, he had very little left for her; partly because in the presence of these stiff, decorous, and well-bred beauties, he went in constant fear lest his tongue, accustomed to the great oaths of the guard-room, should suddenly get the better of him and rap out some word that would appal them. -And yet with all this he combined great pretensions to elegance, to sumptuous dress, and noble bearing. Let the reader reconcile these qualities for himself. I am merely the historian. -He had been standing for some moments, in silence, leaning against the chimney-piece, thinking of something or perhaps of nothing at all, when Fleur-de-Lys suddenly turning round addressed him. After all, it went very much against the poor girls heart to keep up any show of coldness towards him. -Cousin, did you not tell us of a little gipsy girl you had rescued out of the hands of a band of robbers about two months ago, when you were going the counter-watch at night? -I believe I did, fair cousin, answered the captain. -Well, she resumed, perhaps this is the very girl dancing now in the Parvis. Come and see if you recognise her, Cousin Ph?bus. -A secret desire for reconciliation sounded through this gentle invitation to her side, and in the care she took to call him by his name. Captain Ph?bus de Chateaupers (for it is he whom the reader has had before him since the beginning of this chapter) accordingly slowly approached the balcony. -Look, said Fleur-de-Lys, tenderly laying her hand on his arm, look at the girl dancing there in the ring. Is that your gipsy? -Ph?bus looked. Yes, said he, I know her by her goat. -Oh, what a pretty little goat! cried Amelotte, clapping her hands delightedly. -Are its horns real gold? asked Berangre. -Without rising from her seat, Dame Alo?se inquired: Is that one of the band of Bohemians who arrived last year by the Porte Gibard? -Lady mother, said Fleur-de-Lys gently, that gate is now called Porte dEnfer. -Mlle. de Gondelaurier was well aware how much the captain was shocked by her mothers antiquated modes of expression. Indeed, he muttered with a disdainful laugh: Porte Gibard! Porte Gibard! That is to give passage to King Charles VI, no doubt! -Godmother! exclaimed Berangre, whose quick and restless eyes were suddenly attracted to the top of the towers of Notre-Dame. Who is that man in black up there? -All the girls looked up. A man was leaning with his elbows on the topmost parapet of the northern tower which looked towards the Grve. It was a priestas could be seen by his dressand they could clearly distinguish his face, which was resting on his two hands. He stood as motionless as a statue, and in his gaze, fixed steadily on the Place beneath him, there was something of the immobility of the kite looking down upon the sparrows nest it has just discovered. -It is Monsieur the Archdeacon of Josas, said Fleur-de-Lys. -You must have good sight to recognise him at this distance, observed La Gaillefontaine. -How he glares at the little dancer! said Diane de Christeuil. -Then let the Egyptian beware, said Fleur-de-Lys, for he loves not Egypt. -Tis a pity he should look at her like that, added Amelotte de Montmichel, for she dances most bewitchingly. -Cousin Ph?bus, said Fleur-de-Lys impulsively, since you know this gipsy girl, will you not beckon to her to come up hereit will divert us. -Oh, yes! cried the other girls, clapping their hands gleefully. -What a madcap idea! replied Ph?bus. Doubtless she has forgotten me, and I do not even know her name. However, as you wish it, mesdamoiselles, I will see what I can do. And leaning over the balcony he called out, Little one! -The dancing girl was not playing her tambourine at that moment. She turned her head towards the spot from which the voice came, her brilliant eyes caught sight of Ph?bus, and she suddenly stood still. -Little one, repeated the captain, and he motioned to her to come up. -The girl looked at him again, then blushed as if a flame had risen to her cheeks, and taking her tambourine under her arm, she made her way through the gaping crowd towards the door of the house whence Ph?bus called her, her step slow and uncertain, and with the troubled glance of a bird yielding to the fascination of a serpent. -A moment later the tapestry was raised, and the gipsy appeared on the threshold of the room, flushed, shy, panting, her great eyes lowered, not daring to advance a step farther. -Berangre clapped her hands. -But the dancing girl stood motionless in the doorway. Her sudden appearance produced a curious effect on the group. There is no doubt that a vague and indistinct desire to please the handsome officer animated the whole party, and that the brilliant uniform was the target at which they aimed all their coquettish darts; also, from the time of his being present there had arisen among them a certain covert rivalry, scarcely acknowledged to themselves, but which was none the less constantly revealed in their gestures and in their remarks. Nevertheless, as they all possessed much the same degree of beauty, they fought with the same weapons, and each might reasonably hope for victory. The arrival of the gipsy roughly destroyed this equilibrium. Her beauty was of so rare a quality that the moment she entered the room she seemed to illuminate it with a sort of light peculiar to herself. In this restricted space, in this rich frame of sombre hangings and dark panelling, she was incomparably more beautiful and radiant than in the open square. It was like bringing a torch out of the daylight into the shade. The noble maidens were dazzled by her in spite of themselves. Each one felt that her beauty had in some degree suffered. Consequently they instantly and with one accord changed their line of battle (if we may be allowed the term) without a single word having passed between them. For the instincts of women understand and respond to one another far quicker than the intelligence of men. A common foe stood in their midst; they all felt it, and combined for defence. One drop of wine is sufficient to tinge a whole glass of water; to diffuse a certain amount of ill temper throughout a gathering of pretty women, it is only necessary for one still prettier to arrive upon the scene, especially if there is but one man of he company. -Thus the gipsy girls reception was glacial in its coldness. They looked her up and down, then turned to each other, and all was said; they were confederates. Meanwhile the girl, waiting in vain for them to address her, was so covered with confusion that she dared not raise her eyes. -The captain was the first to break the silence. I faith, he said, with his air of fatuous assurance, a bewitching creature! What say you, fair cousin? -This remark, which a more tactful admirer would at least have made in an undertone, was not calculated to allay the feminine jealousy so sharply on the alert in the presence of the gipsy girl. -Fleur-de-Lys answered her fianc in an affected tone of contemptuous indifference, Ah, not amiss. -The others put their heads together and whispered. -At last Madame Alo?se, not the least jealous of the party because she was so for her daughter, accosted the dancer: Come hither, little one. -Come hither, little one, repeated, with comical dignity, Berangre, who would have reached about to her elbow. -The Egyptian advanced towards the noble lady. -Pretty one, said Ph?bus, impressively advancing on his side a step or two towards her, I know not if I enjoy the supreme felicity of being remembered by you; but -She interrupted him, with a smile and a glance of infinite sweetnessOh, yes, she said. -She has a good memory, observed Fleur-de-Lys. -Well, resumed Ph?bus, but you fled in a great hurry that evening. Were you frightened of me? -Oh, no, answered the gipsy. And in the tone of this Oh, no, following on the Oh, yes, there was an indefinable something which stabbed poor Fleur-de-Lys to the heart. -You left in your stead, ma belle, continued the soldier, whose tongue was loosened now that he spoke to a girl of the streets, a wry-faced, one-eyed hunchback varletthe Bishops bell-ringer, by what I can hear. They tell me he is an archdeacons bastard and a devil by birth. He has a droll name tooEmber WeekPalm SundayShrove Tuesdaysomething of that kindsome bell-ringing festival name, at any rate. And so he had the assurance to carry you off, as if you were made for church beadles! It was like his impudence. And what the devil did he want with you, this screech-owl, eh? -I do not know, she answered. -Conceive of such insolence! A bell-ringer to carry off a girl, like a vicomtea clown poaching on a gentlemans preserves! Unheard-of presumption! For the rest, he paid dearly for it. Master Pierrat Torterue is the roughest groom that ever curried a rascal; and I can tell you, for your satisfaction, that your bell-ringers hide got a thorough dressing at his hands. -Poor man! murmured the gipsy, recalling at these words the scene of the pillory. -The captain burst out laughing. Corne de b?uf! your pity is as well-placed as a feather in a sows tail! May I have a paunch like a pope, if He drew up short. Crave your pardon, mesdames! I believe I was on the point of forgetting myself. -Fie, sir! said La Gaillefontaine. -He speaks to this creature in her own language, said Fleur-de-Lys under her breath, her vexation increasing with every moment. Nor was this vexation diminished by seeing the captain delighted with the gipsy girl, but still more with himself, turn on his heel and repeat with blatant and soldier-like gallantry: A lovely creature, on my soul! -Very barbarously dressed! observed Diane de Christeuil, showing her white teeth. -This remark was a flash of light to the others. It showed them where to direct their attack on the gipsy. There being no vulnerable spot in her beauty, they threw themselves upon her dress. -That is very true, said La Montmichel. Pray, how comest thou to be running thus barenecked about the streets, without either gorget or kerchief? -And a petticoat so short as to fill one with alarm, added La Gaillefontaine. -My girl, continued Fleur-de-Lys spitefully, thou wilt certainly be fined for that gold belt. -My poor girl, said Diane, with a cruel smile, if thou hadst the decency to wear sleeves on thy arms, they would not be so burned by the sun. -It was a sight worthy of a more intelligent spectator than Ph?bus, to watch how these high-born maidens darted their envenomed tongues, and coiled and glided and wound serpent-like about the hapless dancing girl. Smiling and cruel, they pitilessly searched and appraised all her poor artless finery of spangles and tinsel. Then followed the heartless laugh, the cutting irony, humiliations without end. Sarcasm, supercilious praise, and spiteful glances descended on the gipsy girl from every side. One might have judged them to be those high-born Roman ladies who amused themselves by thrusting golden pins into the bosom of a beautiful slave, or graceful greyhounds circling with distended nostrils and flaming eyes round some poor hind of the forest, and only prevented by their masters eye from devouring it piecemeal. And what was she after all to these high-born damsels but a miserable dancing girl of the streets? They seemed to ignore the fact of her presence altogether, and spoke of her to her face as of something degraded and unclean, though diverting enough to make jest of. -The Egyptian was not insensible to these petty stings. From time to time a blush of shame burned in her cheek, a flash of anger in her eyes; a disdainful retort seemed to tremble on her lips, and she made the little contemptuous pout with which the reader is familiar. But she remained silent, motionless, her eyes fixed on Ph?bus with a look of resignation infinitely sweet and sad. In this gaze there mingled, too, both joy and tenderness; she seemed to restrain herself for fear of being driven away. -As for Ph?bus, he laughed and took the gipsys part with a mixture of impertinence and pity. -Let them talk, child! he said, jingling his gold spurs. Doubtless your costume is somewhat strange and extravagant; but when a girl is so charming as you, what does it matter? -Mon Dieu! cried La Gaillefontaine, drawing up her swan-like neck, with a bitter smile. It is evident that Messieurs the Kings archers take fire easily at the bright gipsy eyes. -Why not? said Ph?bus. -At this rejoinder, uttered carelessly by the captain, as one throws a stone at random without troubling to see where it falls, Colombe began to laugh and Amolette and Diane and Fleur-de-Lys, though a tear rose at the same time to the eye of the latter. -The gipsy girl, who had dropped her eyes as Colombe and La Gaillefontaine spoke, raised them now all radiant with joy and pride and fixed them again on Ph?bus. At that moment she was dazzlingly beautiful. -The elder lady, while she observed the scene, felt vaguely incensed without knowing exactly why. -Holy Virgin! she suddenly exclaimed, what is this rubbing against my legs? Ah, the horrid beast! -It was the goat, just arrived in search of its mistress, and which, in hurrying towards her, had got its horns entangled in the voluminous folds of the noble ladys gown, which always billowed round her wherever she sat. -This caused a diversion, and the gipsy silently freed the little creature. -Ah, it is the little goat with the golden hoofs! cried Berangre, jumping with joy. -The gipsy girl crouched on her knees and pressed her cheek fondly against the goats sleek head, as if begging its forgiveness for having left it behind. -At this Diane bent over and whispered in Colombes ear: Ah, how did I not think of it before? This is the gipsy girl with the goat. They say she is a witch, and that her goat performs some truly miraculous tricks. -Very well, said Colombe; then let the goat amuse us in its turn, and show us a miracle. -Diane and Colombe accordingly addressed the gipsy eagerly. -Girl, make thy goat perform a miracle for us. -I do not know what you mean, answered the gipsy. -A miraclea conjuring tricka feat of witchcraft, in fact. -I do not understand, she repeated, and fell to caressing the pretty creature again, murmuring fondly, Djali! Djali! -At that moment Fleur-de-Lys remarked a little embroidered leather bag hanging round the goats neck. What is that? she asked of the gipsy. -The gipsy raised her large eyes to her and answered gravely, That is my secret. -Meanwhile the lady of the house had risen. Come, gipsy girl, she exclaimed angrily; if thou and thy goat will not dance for us, what do you here? -Without a word the gipsy rose and turned towards the door. But the nearer she approached it, the more reluctant became her step. An irresistible magnet seemed to hold her back. Suddenly she turned her brimming eyes on Ph?bus, and stood still. -Vrai Dieu! cried the captain, you shall not leave us thus. Come back and dance for us. By-the-bye, sweetheart, how are you called? -Esmeralda, answered the dancing girl, without taking her eyes off him. -At this strange name the girls burst into a chorus of laughter. -Truly a formidable name for a demoiselle! sneered Diane. -You see now, said Amelotte, that she is a sorceress. -Child, exclaimed Dame Alo?se solemnly, your parents never drew that name for you out of the baptismal font! -For some minutes past Berangre, to whom nobody was paying any attention, had managed to entice the goat into a corner with a piece of marchpane, and immediately they had become the best of friends. The inquisitive child had then detached the little bag from the goats neck, opened it, and emptied its contents on to the floor. It was an alphabet, each letter being written separately on a small tablet of wood. No sooner were these toys displayed on the matting than, to the childs delighted surprise, the goat (of whose miracles this was no doubt one) proceeded to separate certain letters with her golden fore-foot, and by dint of pushing them gently about ranged them in a certain order. In a minute they formed a word, which the goat seemed practised in composing, to judge by the ease with which she accomplished the task. Berangre clasped her hands in admiration. -Godmother Fleur-de-Lys, she cried, come and see what the goat has done! -Fleur-de-Lys ran to look, and recoiled at the sight. The letters disposed upon the floor formed the word, -P-H-O-E-B-U-S. -The goat put that word together? she asked excitedly -Yes, godmother, answered Berangre. It was impossible to doubt it; the child could not spell. -So this is the secret, thought Fleur-de-Lys. -By this time the rest of the party had come forward to lookthe mother, the girls, the gipsy, the young soldier. -The Bohemian saw the blunder the goat had involved her in. She turned red and white, and then began to tremble like a guilty creature before the captain, who gazed at her with a smile of satisfaction and astonishment. -Ph?bus! whispered the girls in amazement; that is the name of the captain! -You have a wonderful memory! said Fleur-de-Lys to the stupefied gipsy girl. Then, bursting into tears: Oh, she sobbed, she is a sorceress! While a still more bitter voice whispered in her inmost heart, She is a rival! And she swooned in her mothers arms. -My child! my child! cried the terrined mother. Begone, diabolical gipsy! -In a trice Esmeralda gathered up the unlucky letters, made a sign to Djali, and quitted the room by one door, as they carried Fleur-de-Lys out by another. -Captain Ph?bus, left alone, hesitated a moment between the two doorsthen followed the gipsy girl. -______________________ -1 A popular French poet of the sixteenth century, whose poem on The Divine Week and Works was translated by Joshua Sylvester in the reign of James I. -Chapter 2 - Showing that A Priest and A Philosopher are not the same -The priest whom the young girls had remarked leaning over the top of the north tower of the Cathedral and gazing so intently at the gipsys dancing, was no other than the Archdeacon Claude Frollo. -Our readers have not forgotten the mysterious cell which the archdeacon had appropriated to himself in this tower. (By the way, I do not know but what it is the same, the interior of which may be seen to this day through a small square window, opening to the east at about a mans height from the floor upon the platform from which the towers springa mere den now, naked, empty, and falling to decay, the ill-plastered walls of which are decorated here and there, at the present moment, by some hideous yellow engravings of cathedral fronts. I presume that this hole is jointly inhabited by bats and spiders, so that a double war of extermination is being carried on there against the flies.) -Every day, an hour before sunset, the archdeacon mounted the stair of the tower and shut himself up in this cell, where he sometimes spent whole nights. On this day, just as he reached the low door of his retreat and was preparing to insert in the lock the small and intricate key he always carried about with him in the pouch hanging at his side, the jingle of a tambourine and of castanets suddenly smote on his ear, rising up from the Place du Parvis. The cell, as we have said, had but one window looking over the transept roof. Claude Frollo hastily withdrew the key, and in another moment was on the summit of the tower, in that gloomy and intent attitude in which he had been observed by the group of girls. -There he stood, grave, motionless, absorbed in one object, one thought. All Paris was spread out at his feet, with her thousand turrets, her undulating horizon, her river winding under the bridges, her stream of people flowing to and fro in the streets; with the cloud of smoke rising from her many chimneys; with her chain of crested roofs pressing in ever tightening coils round about Notre-Dame. But in all that great city the Archdeacon beheld but one spotthe Place du Parvis; and in that crowd but one figurethat of the gipsy girl. -It would have been difficult to analyze the nature of that gaze, or to say whence sprang the flame that blazed in it. His eyes were fixed and yet full of anguish and unrest; and from the profound immobility of his whole body, only faintly agitated now and then by an involuntary tremor, like a tree shaken by the wind; from his rigid arms, more stony than the balustrade on which they leaned, and the petrified smile that distorted his countenance, you would have said that nothing of Claude Frollo was alive save his eyes. -The gipsy girl was dancing and twirling her tambourine on the tip of her finger, throwing it aloft in the air while she danced the Proven?al saraband; agile, airy, joyous, wholly unconscious of the sinister gaze falling directly on her head. -The crowd swarmed round her; from time to time, a man tricked out in a long red and yellow coat, went round to keep the circle clear, and then returned to a seat a few paces from the dancer, and took the head of the goat upon his knee. This man appeared to be the companion of the gipsy girl. Claude Frollo, from his elevated position, could not distinguish his features. -No sooner had the Archdeacon caught sight of this individual, than his attention seemed divided between him and the dancer, and his face became more and more overcast. Suddenly he drew himself up, and a tremor ran through his whole frame. Who can that man be? he muttered between his teeth; I have always seen her alone hitherto. -He then vanished under the winding roof of the spiral staircase, and proceeded to descend. As he passed the half-open door of the belfry, he saw something which made him pause. It was Quasimodo, leaning out of an opening in one of the great projecting slate eaves and likewise looking down into the Place, but so profoundly absorbed in contemplation that he was unaware of the passing of his adopted father. His savage eye had a singular expressiona mingled look of fondness and delight. -How strange! murmured Claude. Can he too be looking at the Egyptian? He continued his descent, and in a few moments the troubled Archdeacon entered the Place by the door at the bottom of the tower. -What has become of the gipsy? said he, as he mingled with the crowd which the sound of the tambourine had drawn together. -I know not, answered a bystander; she has just disappeared. They called to her from the house opposite, and so I think she must have gone to dance some fandango there. -Instead of the Egyptian, on the same carpet, of which the arabesques but a moment before seemed to vanish beneath the fantastic weavings of her dances, the Archdeacon now beheld only the red and yellow man; who, in order to earn an honest penny in his turn, was parading round the circle, his arms akimbo, his head thrown back, very red in the face, and balancing a chair between his teeth. On this chair he had fastened a cat which a woman in the crowd had lent him, and which was swearing with fright. -Notre-Dame! cried the Archdeacon, as the mounte-bank, the perspiration pouring off his face, passed before him with his pyramid of cat and chairWhat does Ma?tre Pierre Gringoire here? -The stern voice of the Archdeacon so startled the poor devil that he lost his balance, and with it his whole erection, and the chair and the cat came toppling over right on to the heads of the spectators and in the midst of a deafening uproar. -It is probable that Pierre Gringoire (for it was indeed he) would have had a fine account to settle with the owner of the cat, not to speak of all the bruised and scratched faces round him, had he not hastily availed himself of the tumult and taken refuge in the Cathedral, whither Claude Frollo beckoned him to follow. -The Cathedral was already dark and deserted, the transepts were full of deepest shadow, and the lamps of the chapels were beginning to twinkle like stars under the black vault of the roof. The great central rose-window alone, whose thousand tints were flooded by a horizontal stream of evening sunshine, gleamed in the shadow like a star of diamonds and cast its dazzling image on the opposite side of the nave. -When they had proceeded a few steps, Dom Claude leaned against a pillar and regarded Gringoire steadfastly. This look was not the one Gringoire had feared to encounter in his shame at being surprised by so grave and learned a personage in his merry-andrew costume. There was in the priests gaze no touch of disdain or mockery; it was serious, calm, and searching. The Archdeacon was the first to break silence. -Now, Ma?tre Pierre, you have many things to explain to me. And first, how comes it that I have seen nothing of you for the last two months, and then find you in the public street in noble guise i sooth!part red, part yellow, like a Caudebec apple! -Messire, answered Gringoire plaintively, it is in very truth a preposterous outfit, and you behold me about as comfortable as a cat with a pumpkin on its head. It is, I acknowledge, an ill deed on my part to expose the gentlemen of the watch to the risk of belabouring, under this motley coat, the back of a Pythagorean philosopher. But what would you, my reverend master? The fault lies with my old doublet, which basely deserted me at the beginning of winter under the protest that it was falling in rags, and that it was under the necessity of reposing itself in the ragmans pack. Que faire? Civilization has not yet reached that point that one may go quite naked, as old Diogenes would have wished. Add to this that the wind blew very cold, and the month of January is not the season to successfully initiate mankind into this new mode. This coat offered itself, I accepted it, and abandoned my old black tunic, which, for a hermetic such as I am, was far from being hermetically closed. Behold me then, in my buffoons habit, like Saint-Genestus. What would you have?it is an eclipse. Apollo, as you know, tended the flocks of Admetes. -A fine trade this you have adopted! remarked the Archdeacon. -I admit, master, that it is better to philosophize and poetize, to blow fire in a furnace or receive it from heaven, than to be balancing cats in the public squares. And when you suddenly addressed me, I felt as stupid as an ass in front of a roasting-pit. But whats to be done, messire? One must eat to live, and the finest Alexandrine verses are nothing between the teeth as compared with a piece of cheese. Now, I composed for the Lady Margaret of Flanders that famous epithalamium, as you know, and the town has not paid me for it, pretending that it was not good enough; as if for four crowns you could give them a tragedy of Sophocles! Hence, see you, I was near dying of hunger. Happily I am fairly strong in the jaws; so I said to my jaw: Perform some feats of strength and equilibriumfeed yourself. Ale te ipsam. A band of vagabonds who are become my very good friends, taught me twenty different herculean feats; and now I feed my teeth every night with the bread they have earned in the day. After all, concedo, I concede that it is but a sorry employ of my intellectual faculties, and that man is not made to pass his life in tambourining and carrying chairs in his teeth. But, reverend master, it is not enough to pass ones life; one must keep it. -Dom Claude listened in silence. Suddenly his deep-set eye assumed so shrewd and penetrating an expression that Gringoire felt that the innermost recesses of his soul were being explored. -Very good, Master Pierre; but how is it that you are now in company with this Egyptian dancing girl? -Faith! returned Gringoire, because she is my wife and I am her husband. -The priests sombre eyes blazed. -And hast thou done that, villain! cried he, grasping Gringoire furiously by the arm; hast thou been so abandoned of God as to lay hand on this girl? -By my hope of paradise, reverend sir, replied Gringoire, trembling in every limb, I swear to you that I have never touched her, if that be what disturbs you. -What then is thy talk of husband and wife? said the priest. -Gringoire hastened to relate to him as succinctly as possible what the reader already knows: his adventure in the Court of Miracles and his broken-pitcher marriage. The marriage appeared as yet to have had no result whatever, the gipsy girl continuing every night to defraud him of his conjugal rights as on that first one. Tis mortifying, and thats the truth, he concluded; but it all comes of my having had the ill-luck to espouse a virgin. -What do you mean? asked the Archdeacon, whom the tale gradually tranquillized. -It is difficult to explain, returned the poet. There is superstition in it. My wife, as an old thief among us called the Duke of Egypt has told me, is a foundlingor a lostling, which is the same thing. She wears about her neck an amulet which, they declare, will some day enable her to find her parents again, but which would lose its virtue if the girl lost hers. Whence it follows that we both of us remain perfectly virtuous. -Thus, you believe, Ma?tre Pierre, resumed Claude, whose brow was rapidly clearing, that this creature has never yet been approached by any man? -Why, Dom Claude, how should a man fight against a superstition? She has got that in her head. I hold it to be rare enough to find this nunlike prudery keeping itself so fiercely aloof among all these easily conquered gipsy girls. But she has three things to protect her: the Duke of Egypt, who has taken her under his wing, reckoning, may-be, to sell her later on to some fat abbot or other; her whole tribe, who hold her in singular veneration, like the Blessed Virgin herself; and a certain pretty little dagger, which the jade always carries about with her, despite the provosts ordinances, and which darts out in her hand when you squeeze her waist. Tis a fierce wasp, believe me! -The Archdeacon pressed Gringoire with questions. -By Gringoires account, Esmeralda was a harmless and charming creature; pretty, apart from a little grimace which was peculiar to her; artless and impassioned; ignorant of everything and enthusiastic over everything; fond above all things of dancing, of all the stir and movement of the open air; not dreaming as yet of the difference between man and woman; a sort of human bee, with invisible wings to her feet, and living in a perpetual whirlwind. She owed this nature to the wandering life she had led. Gringoire had ascertained that, as quite a little child, she had gone all through Spain and Catalonia, and into Sicily; he thought even that the caravan of Zingari, to which she belonged, had carried her into the kingdom of Algiersa country situated in Achaia, which Achaia was adjoining on one side to lesser Albania and Greece, and on the other to the sea of the Sicilies, which is the way to Constantinople. The Bohemians, said Gringoire, were vassals of the King of Algeria, in his capacity of Chief of the nation of the White Moors. Certain it was that Esmeralda had come into France while yet very young by way of Hungary. From all these countries the girl had brought with her fragments of fantastic jargons, outlandish songs and ideas which made her language almost as motley as her half-Egyptian, half-Parisian costume. For the rest, the people of the quarters which she frequented loved her for her gaiety, her kindness, her lively ways, for her dancing and her songs. In all the town she believed herself to be hated by two persons only, of whom she often spoke with dread: the sachette of the Tour-Roland, an evil-tempered recluse who cherished an unreasoning malice against gipsies, and who cursed the poor dancer every time she passed before her window; and a priest, who never crossed her path without hurling at her words and looks that terrified her. This last circumstance perturbed the Archdeacon greatly, though Gringoire paid no heed to the fact, the two months that had elapsed having sufficed to obliterate from the thoughtless poets mind the singular details of that evening on which he had first encountered the gipsy girl, and the circumstance of the Archdeacons presence on that occasion. For the rest, the little dancer feared nothing; she did not tell fortunes, and consequently was secure from those persecutions for magic so frequently instituted against the gipsy women. And then Gringoire was at least a brother to her, if he could not be a husband. After all, the philosopher endured very patiently this kind of platonic marriage. At all events it insured him food and a lodging. Each morning he set out from the thieves quarter, most frequently in company with the gipsy girl; he helped her to gain her little harvest of small coin in the streets; and each evening they returned to the same roof, he let her bolt herself into her own little chamber, and then slept the sleep of the just. A very agreeable existence on the whole, said he, and very favourable to reflection. Besides, in his heart and inner conscience, the philosopher was not quite sure that he was desperately in love with the gipsy. He loved her goat almost as much. It was a charming beast, gentle, intelligent, not to say intellectual; a goat of parts. (Nothing was commoner in the Middle Ages than these trained animals, which created immense wonderment among the uninitiated, but frequently brought their instructor to the stake.) However, the sorceries of the goat with the gilded hoofs were of a very innocent nature. Gringoire explained them to the Archdeacon, who appeared strangely interested in these particulars. In most cases it was sufficient to present the tambourine to the goat in such or such a manner, for it to perform the desired trick. It had been trained to this by its mistress, who had such a singular talent for these devices that two months had sufficed her to teach the goat to compose, with movable letters, the word Ph?bus. -Ph?bus! said the priest; why Ph?bus? -I do not know, answered Gringoire. Perhaps it is a word that she thinks endowed with some magic and secret virtue. She often murmurs it to herself when she believes herself alone. -Are you sure, rejoined Claude, with his searching look, that it is only a wordthat it is not a name? -The name of whom? said the poet. -How should I know? said the priest. -This is what I imagine, messire. These Bohemians are something of Guebers, and worship the sun: hence this Ph?bus. -That does not seem so evident to me as it does to you, Ma?tre Pierre. -After all, its no matter to me. Let her mumble her Ph?bus to her hearts content. What I know for certain is that Djali loves me already almost as much as her mistress. -Who is Djali? -That is the goat. -The Archdeacon leant his chin on his hand and seemed to reflect for a moment. Suddenly he turned brusquely to Gringoire: -And you swear to me that you have not touched her? -Whom? asked Gringoire; the goat? -No, this woman. -My wife? I swear I have not. -And yet you are often alone with her. -Every night for a full hour. -Dom Claude frowned. Oh! oh! Solus cum sola non cogitabuntur orare Pater Noster.1 -By my soul, I might say Paters and Ave Marias and the Credo without her paying any more attention to me than a hen to a church. -Swear to me, by thy mothers body, said the Archdeacon vehemently, that thou hast not so much as touched that woman with the tip of thy finger. -I will swear it too by my fathers head, for the two things have more than one connection. But, reverend master, permit me one question in return. -Speak, sir. -What does that signify to you? -The Archdeacons pale face flushed like the cheek of a young girl. He was silent for a moment, and then replied with visible embarrassment: -Hark you, Ma?tre Pierre Gringoire. You are not yet damned, as far as I know. I am interested in you, and wish you well. Now, the slightest contact with that demon of a gipsy girl will infallibly make you a servant of Satanas. You know tis always the body that ruins the soul. Woe betide you if you come nigh that woman! I have spoken. -I did try it once, said Gringoire, scratching his ear. That was on the first day, but I only got stung for my pains. -You had that temerity, Ma?tre Gringoire? and the priests brow darkened again. -Another time, continued the poet, with a grin, before I went to bed, I looked through her key-hole, and beheld the most delicious damsel in her shift that ever made a bedstead creak under her naked foot. -To the foul fiend with thee! cried the priest, with a look of fury; and thrusting the amazed Gringoire from him by the shoulder, he plunged with long strides into the impenetrable gloom of the Cathedral arches. -______________________ -1 A man and a woman alone together will not think of saying Pater Nosters. -Chapter 3 - The Bells -Since his taste of the pillory, the neighbours in the vicinity of Notre-Dame thought they perceived a remarkable abatement in Quasimodos rage for bell-ringing. Before that time the smallest excuse set the bells goinglong morning chimes that lasted from prime to compline; full peals for a high mass, full-toned runs flashing up and down the smaller bells for a wedding or a christening, and filling the air with an exquisite network of sweet sound. The ancient minster, resonant and vibrating to her foundations, lived in a perpetual jubilant tumult of bells. Some self-willed spirit of sound seemed to have entered into her and to be sending forth a never-ending song from all those brazen throats. And now that spirit had departed. The Cathedral seemed wilfully to maintain a sullen silence. Festivals and burials had their simple accompaniment, plain and meagrewhat the Church demandednot a note beyond. Of the two voices that proceed from a churchthat of the organ within and the bells withoutonly the organ remained. It seemed as though there were no longer any musicians in the belfries. Nevertheless, Quasimodo was still there; what had come over him? Was it that the shame and despair of the pillory still lingered in his heart, that his soul still quivered under the lash of the torturer, that his horror of such treatment had swallowed up all other feeling in him, even his passion for the bells?or was it rather that Marie had a rival in the heart of the bell-ringer of Notre-Dame, and that the great bell and her fourteen sisters were being neglected for something more beautiful? -It happened that in this year of grace 1482, the Feast of the Annunciation fell on Tuesday, the 25th of March. On that day the air was so pure and light that Quasimodo felt some return of affection for his bells. He accordingly ascended the northern tower, while the beadle below threw wide the great doors of the church, which consisted, at that time, of enormous panels of strong wood, padded with leather, bordered with gilded iron nails, and framed in carving very skilfully wrought. -Arrived in the lofty cage of the bells, Quasimodo gazed for some time with a sorrowful shake of the head at his six singing birds, as if he mourned over something alien that had come between him and his old loves. But when he had set them going, when he felt the whole cluster of bells move under his hands, when he sawfor he could not hear itthe palpitating octave ascending and descending in that enormous diapason, like a bird fluttering from bough to boughwhen the demon of music, with his dazzling shower of stretti, trills, and arpeggios, had taken possession of the poor deaf creature, then he became happy once more, he forgot his former woes, and as the weight lifted from his heart his face lit up with joy. -To and fro he hurried, clapped his hands, ran from one rope to the other, spurring on his six singers with mouth and hands, like the conductor of an orchestra urging highly trained musicians. -Come, Gabrielle, said he, come now, pour all thy voice into the Place, to-day is high festival. Thibauld, bestir thyself, thou art lagging behind; on with thee, art grown rusty, sluggard? That is wellquick! quick! let not the clapper be seen. Make them all deaf like me. Thats the way, my brave Thibauld! Guillaume! Guillaume! thou art the biggest, and Pasquier is the smallest, and yet Pasquier works better than thou. I warrant that those who can hear would say so too. Right so, my Gabrielle! louder, louder! Hey! you two up there, you sparrows, what are you about? I do not see you make the faintest noise? What ails those brazen beaks of yours that look to be yawning when they should be singing? Up, up, to your work! Tis the Feast of the Annunciation. The sun shines bright, and well have a merry peal. What, Guillaume! Out of breath, my poor fat one! -He was entirely absorbed in urging on his bells, the whole six of them rearing and shaking their polished backs like a noisy team of Spanish mules spurred forward by the cries of the driver. -Happening, however, to glance between the large slate tiles which cover, up to a certain height, the perpendicular walls of the steeple, he saw down in the square a fantastically dressed girl spreading out a carpet, on which a little goat came and took up its position and a group of spectators formed a circle round. This sight instantly changed the current of his thoughts, and cooled his musical enthusiasm as a breath of cold air congeals a stream of flowing resin. He stood still, turned his back on the bells, and crouching down behind the slate eaves fixed on the dancer that dreamy, tender, and softened look which once already had astonished the Archdeacon. Meanwhile the neglected bells suddenly fell silent, to the great disappointment of lovers of carillons who were listening in all good faith from the Pont-au-Change, and now went away as surprised and disgusted as a dog that has been offered a bone and gets a stone instead. -Chapter 4 - Fate -One fine morning in this same month of Marchit was Saturday, the 29th, St. Eustaches Day, I thinkour young friend, Jehan Frollo of the Mill, discovered, while putting on his breeches, that his purse gave forth no faintest chink of coin. Poor purse! said he, drawing it out of his pocket, what, not a single little parisis? How cruelly have dice, Venus, and pots of beer disembowelled thee! Behold thee empty, wrinkled, and flabby, like the bosom of a fury! I would ask you, Messer Cicero and Messer Seneca, whose dogeared volumes I see scattered upon the floor, of what use is it for me to know better than any master of the Mint or a Jew of the Pont-au-Changeurs, that a gold crown piece is worth thirty-five unzain at twenty-five sous eight deniers parisis each, if I have not a single miserable black liard to risk upon the double-six? Oh, Consul Cicero! this is not a calamity from which one can extricate ones self by periphrasesby quemadmodum, and verum enim vero! -He completed his toilet dejectedly. An idea occurred to him as he was lacing his boots which he at first rejected: it returned, however, and he put on his vest wrong side out, a sure sign of a violent inward struggle. At length he cast his cap vehemently on the ground, and exclaimed: Be it so! the worst has come to the worstI shall go to my brother. I shall catch a sermon, I know, but also I shall catch a crown piece. -He threw himself hastily into his fur-edged gown, picked up his cap, and rushed out with an air of desperate resolve. -He turned down the Rue de la Harpe towards the City. Passing the Rue de la Huchette, the odour wafted from those splendid roasting-spits which turned incessantly, tickled his olfactory nerves, and he cast a lustful eye into the Cyclopean kitchen which once extorted from the Franciscan monk, Calatigiron, the pathetic exclamation: Veramente, queste rotisscrie sono cosa stupenda! But Jehan had not the wherewithal to obtain a breakfast, so with a profound sigh he passed on under the gateway of the Petit-Chatelet, the enormous double trio of massive towers guarding the entrance to the City. -He did not even take time to throw the customary stone at the dishonoured statue of that Perinet Leclerc who betrayed the Paris of Charles VI to the English, a crime which his effigy, its face all battered with stones and stained with mud, expiated during three centuries at the corner of the streets de la Harpe and de Bussy, as on an everlasting pillory. -Having crossed the Petit-Pont and walked down the Rue Neuve-Sainte-Genevive, Jehan de Molendino found himself in front of Notre-Dame. Then all his indecision returned, and he circled for some minutes round the statue of Monsieur Legris, repeating to himself with a tortured mind: -The sermon is certain, the florin is doubtful. -He stopped a beadle who was coming from the cloister. Where is Monsieur the Archdeacon of Josas? -In his secret cell in the tower, I believe, answered the man; but I counsel you not to disturb him, unless you come from some one such as the Pope or the King himself. -Jehan clapped his hands. -Bdiable! what a magnificent chance for seeing the famous magicians cave! -This decided him, and he advanced resolutely through the little dark doorway, and began to mount the spiral staircase of Saint-Gilles, which leads to the upper stories of the tower. -We shall see! he said as he proceeded. By the pangs of the Virgin! it must be a curious place, this cell which my reverend brother keeps so strictly concealed. They say he lights up hells own fires there on which to cook the philosophers stone. Bdieu! I care no more for the philosophers stone than for a pebble; and Id rather find on his furnace an omelet of Easter eggs in lard, than the biggest philosophers stone in the world! -Arrived at the gallery of the colonnettes, he stopped a moment to take breath and to call down ten million cartloads of devils on the interminable stairs. He then continued his ascent by the narrow doorway of the northern tower, now prohibited to the public. A moment or two after passing the belfry, he came to a small landing in a recess with a low Gothic door under the vaulted roof, while a loophole opposite in the circular wall of the staircase enabled him to distinguish its enormous lock and powerful iron sheeting. Any one curious to inspect this door at the present day will recognise it by this legend inscribed in white letters on the black wall: Jadore Coralie, 1823. Sign, Ugne. (This sign is included in the inscription.) -Whew! said the scholar; this must be it. -The key was in the lock, the door slightly ajar; he gently pushed it open and poked his head round it. -The reader is undoubtedly acquainted with the works of Rembrandtthe Shakespeare of painting. Among the many wonderful engravings there is one etching in particular representing, as is supposed, Doctor Faustus, which it is impossible to contemplate without measureless admiration. There is a gloomy chamber; in the middle stands a table loaded with mysterious and repulsive objectsdeaths heads, spheres, alembics, compasses, parchments covered with hieroglyphics. Behind this table, which hides the lower part of him, stands the Doctor wrapped in a wide gown, his head covered by a fur cap reaching to his eyebrows. He has partly risen from his immense arm-chair, his clenched fists are leaning on the table, while he gazes in curiosity and terror at a luminous circle of magic letters shining on the wall in the background like the solar spectrum in a camera obscura. This cabalistic sun seems actually to scintillate, and fills the dim cell with its mysterious radiance. It is horrible and yet beautiful. -Something very similar to Fausts study presented itself to Jehans view as he ventured his head through the half-open door. Here, too, was a sombre, dimly lighted cell, a huge arm-chair, and a large table, compasses, alembics, skeletons of animals hanging from the ceiling, a celestial globe rolling on the floor, glass phials full of quivering gold-leaf, skulls lying on sheets of vellum covered with figures and written characters, thick manuscripts open and piled one upon another regardless of the creased corners of the parchment; in short, all the rubbish of sciencedust and cobwebs covering the whole heap. But there was no circle of luminous letters, no doctor contemplating in ecstasy the flamboyant vision as an eagle gazes at the sun. -Nevertheless the cell was not empty. A man was seated in the arm-chair, leaning over the table. Jehan could see nothing but his broad shoulders and the back of his head; but he had no difficulty in recognising that bald head, which nature seemed to have provided with a permanent tonsure, as if to mark by this external sign the irresistible clerical vocation of the Archdeacon. -Thus Jehan recognised his brother; but the door had been opened so gently that Dom Claude was unaware of his presence. The prying little scholar availed himself of this opportunity to examine the cell for a few minutes at his ease. A large furnace, which he had not remarked before, was to the left of the arm-chair under the narrow window. The ray of light that penetrated through this opening traversed the circular web of a spider, who had tastefully woven her delicate rosace in the pointed arch of the window and now sat motionless in the centre of this wheel of lace. On the furnace was a disordered accumulation of vessels of every description, stone bottles, glass retorts, and bundles of charcoal. Jehan observed with a sigh that there was not a single cooking utensil. -In any case there was no fire in the furnace, nor did any appear to have been lighted there for a long time. A glass mask which Jehan noticed among the alchemistic implements, used doubtless to protect the archdeacons face when he was engaged in compounding some deadly substance, lay forgotten in a corner, thick with dust. Beside it lay a pair of bellows equally dusty, the upper side of which bore in letters of copper the motto: Spiro, spero.1 -Following the favourite custom of the hermetics, the walls were inscribed with many legends of this description; some traced in ink, others engraved with a metal point; Gothic characters, Hebrew, Greek and Roman, pell-mell; inscribed at random, overlapping each other, the more recent effacing the earlier ones, and all interlacing and mingled like the branches of a thicket or the pikes in a mle. And, in truth, it was a confused fray between all the philosophies, all the schemes, the wisdom of the human mind. Here and there one shone among the others like a banner among the lanceheads, but for the most part they consisted of some brief Latin or Greek sentence, so much in favour in the Middle Ages, such as: Unde? Inde?Homo homini monstrum.Astra, castra.Nomen, numen. Mega biblion, mega kakon.Sapere aude.Flat ubi vult, etc.2 Or sometimes a word devoid of all meaning as 'Alagcofagia which perhaps concealed some bitter allusion to the rules of the cloister; sometimes a simple maxim of monastic discipline set forth in a correct hexameter: C?lestem Dominum, terrestrem dicite domnum.3 Here and there, too, were obscure Hebrew passages, of which Jehan, whose Greek was already of the feeblest, understood nothing at all; and the whole crossed and recrossed in all directions with stars and triangles, human and animal figures, till the wall of the cell looked like a sheet of paper over which a monkey has dragged a pen full of ink. -Altogether the general aspect of the study was one of complete neglect and decay; and the shocking condition of the implements led inevitably to the conclusion that their owner had long been diverted from his labours by pursuits of some other kind. -The said owner, meanwhile, bending over a vast manuscript adorned with bizarre paintings, appeared to be tormented by some idea which incessantly interrupted his meditations. So at least Jehan surmised as he listened to his musing aloud, with the intermittent pauses of a person talking in his dreams. -Yes, he exclaimed, Manou said it, and Zoroaster taught the same! the sun is born of fire, the moon of the sun. Fire is the soul of the Great All, its elementary atoms are diffused and constantly flowing by an infinity of currents throughout the universe. At the points where these currents cross each other in the heavens, they produce light; at their points of intersection in the earth, they produce gold. Lightgold; it is the same thingfire in its concrete state; merely the difference between the visible and the palpable, the fluid and the solid in the same substance, between vapour and icenothing more. This is no dream; it is the universal law of Nature. But how to extract from science the secret of this universal law? What! this light that bathes my hand is gold! All that is necessary is to condense by a certain law these same atoms dilated by certain other laws! Yes; but how? Some have thought of burying a ray of sunshine. Averro?syes, it was Averro?sburied one under the first pillar to the left of the sanctuary of the Koran, in the great Mosque of Cordova; but the vault was not to be opened to see if the operation was successful under eight thousand years. -Diable! said Jehan to himself, rather a long time to wait for a florin! -Others have thought, continued the Archdeacon musingly, that it were better to experiment upon a ray from Sirius. But it is difficult to obtain this ray pure, on account of the simultaneous presence of other stars whose rays mingle with it. Flamel considers it simpler to operate with terrestrial fire. Flamel! theres predestination in the very name! Flamma! yes, firethat is all. The diamond exists already in the charcoal, gold in fire But how to extract it? Magistri affirms that there are certain female names which possess so sweet and mysterious a charm, that it suffices merely to pronounce them during the operation. Let us see what Manou says on the subject: Where women are held in honour, the gods are well pleased: where they are despised, it is useless to pray to God. The mouth of a woman is constantly pure; it is as a running stream, as a ray of sunshine. The name of a woman should be pleasing, melodious, and give food to the imaginationshould end in long vowels, and sound like a benediction. Yes, yes, the sage is right; for example, MariaSophiaEsmeral Damnation! Ever that thought! -And he closed the book with a violent slam. -He passed his hand over his brow as if to chase away the thought that haunted him. Then taking from the table a nail and a small hammer, the handle of which bore strange, painted, cabalistic figures -For some time, said he with a bitter smile, I have failed in all my experiments. A fixed idea possesses me, and tortures my brain like the presence of a fiery stigma. I have not even succeeded in discovering the secret of Cassiodorus, whose lamp burned without wick or oil. Surely a simple matter enough! -The devil it is! muttered Jehan between his teeth. -One miserable thought, then, continued the priest, suffices to sap a mans will and render him feeble-minded. Oh, how Claude Pernelle would mock at meshe who could not for one moment divert Nicholas Flamel from the pursuit of his great work! What! I hold in my hand the magic hammer of Zechieles! At every blow which, from the depths of his cell, the redoubtable rabbi struck with this hammer upon this nail that one among his enemies whom he had condemned would, even were he two thousand leagues away, sink an arms length into the earth which swallowed him up. The King of France himself, for having one night inadvertently struck against the door of the magician, sank up to his knees in his own pavement of Paris. This happened not three centuries ago. Well, I have the hammer and the nail, and yet these implements are no more formidable in my hands than a hammer in the hand of a smith. And yet all that is wanting is the magic word which Zechieles pronounced as he struck upon the nail. -A mere trifle! thought Jehan. -Come let us try, resumed the Archdeacon eagerly. If I succeed, I shall see the blue spark fly from the head of the nail. Emen-Hten! That ns not itSigeani! Sigeani! May this nail open the grave for whomsoever bears the name of Ph?bus! A curse upon it again! Forever that same thought! -He threw away the hammer angrily. He then sank so low in his arm-chair and over the table that Jehan lost sight of him. For some minutes he could see nothing but a hand clenched convulsively on a book. Suddenly Dom Claude arose, took a pair of compasses, and in silence engraved upon the wall in capitals the Greek word: -ANGKH. -My brothers a fool, said Jehan to himself; it would have been much simpler to write Fatum. Everybody is not obliged to know Greek. -The Archdeacon reseated himself in his chair and clasped his forehead between his two hands, like a sick person whose head is heavy and burning. -The scholar watched his brother with surprise. He had no conceptionhe who always wore his heart upon his sleeve, who observed no laws but the good old laws of nature, who allowed his passions to flow according to their natural tendencies, and in whom the lake of strong emotions was always dry, so many fresh channels did he open for it dailyhe had no conception with what fury that sea of human passions ferments and boils when it is refused all egress; how it gathers strength, swells, and overflows; how it wears away the heart; how it breaks forth in inward sobs and stifled convulsions, until it has rent its banks and overflowed its bed. The austere and icy exterior of Claude Frollo, that cold surface of rugged and inaccessible virtue, had always deceived Jehan. The light-hearted scholar had never dreamed of the lava, deep, boiling, furious, beneath the snow of ?tna. -We do not know whether any sudden perception of this kind crossed Jehans mind; but, scatter-brained as he was, he understood that he had witnessed something he ought never to have seen; that he had surprised the soul of his elder brother in one of its most secret attitudes, and that Claude must not discover it. Perceiving that the Archdeacon had fallen back into his previous immobility, he withdrew his head very softly and made a slight shuffling of feet behind the door, as of some one approaching and giving warning of the fact. -Come in! cried the Archdeacon, from within his cell. I was expecting you, and left the key in the door on purpose. Come in, Ma?tre Jacques! -The scholar entered boldly. The Archdeacon much embarrassed by such a visitor in this particular place started violently in his arm-chair. -What! it is you, Jehan? -A J at any rate, said the scholar, with his rosy, smiling, impudent face. -The countenance of Dom Claude had resumed its severe expression. What are you doing here? -Brother, answered the scholar, endeavouring to assume a sober, downcast, and modest demeanour, and twisting his cap in his hands with an appearance of artlessness, I have come to beg of you. -What? -A moral lesson of which I have great need, he had not the courage to addand a little money of which my need is still greater. The last half of his sentence remained unspoken. -Sir, said the Archdeacon coldly, I am greatly displeased with you. -Alas! sighed the scholar. -Dom Claude described a quarter of a circle with his chair, and regarded Jehan sternly. I am very glad to see you. -This was a formidable exordium. Jehan prepared for a sharp encounter. -Jehan, every day they bring me complaints of you. What is this about a scuffle in which you belaboured a certain little vicomte, Albert de Ramonchamp? -Oh, said Jehan, a mere trifle! An ill-conditioned page, who amused himself with splashing the scholars by galloping his horse through the mud. -And what is this about Mahiet Fargel, whose gown you have torn? Tunicam dechiraverunt, says the charge. -Pah! a shabby Montaigu cape. Whats there to make such a coil about? -The complaint says tunicam, not cappettam. Do you understand Latin? -Jehan did not reply. -Yes, went on the priest shaking his head, this is what study and letters have come to now! The Latin tongue is scarcely understood, Syriac unknown, the Greek so abhorred that it is not accounted ignorance in the most learned to miss over a Greek word when reading, and to say, Gr?cum est non legitur. -The scholar raised his eyes boldly. Brother, shall I tell you in good French the meaning of that Greek word over there upon the wall? -Which word? -ANGKH. -A faint flush crept into the parchment cheeks of the Archdeacon, like a puff of smoke giving warning of the unseen commotions of a volcano. The scholar hardly noted it. -Well, Jehan, faltered the elder brother with an effort, what does the word mean? -Fatality. -Dom Claude grew pale again, and the scholar went on heedlessly: -And the word underneath it, inscribed by the same hand, Anagneia signifies impurity. You see, we know our Greek. -The Archdeacon was silent. This lesson in Greek had set him musing. -Little Jehan, who had all the cunning of a spoilt child, judged the moment favourable for hazarding his request. Adopting, therefore, his most insinuating tones, he began: -Do you hate me so much, good brother, as to look thus grim on account of a few poor scufflings and blows dealt all in fair fight with a pack of boys and young monkeysquibusdam marmosetis? You see, good brother Claude, we know our Latin. -But this caressing hypocrisy failed in its customary effect on the severe elder brother. Cerberus would not take the honeyed sop. Not a furrow in the Archdeacons brow was smoothed. What are you aiming at? he asked dryly. -Well, then, to be plain, it is this, answered Jehan stoutly, I want money. -At this piece of effrontery the Archdeacon at once became the school-master, the stern parent. -You are aware, Monsieur Jehan, that our fief of Tirechappe, counting together both the ground rents and the rents of the twenty-one houses, only brings in twenty-nine livres, eleven sous, six deniers parisis. That is half as much again as in the time of the brothers Paclet, but it is not much. -I want some money, repeated Jehan stolidly. -You know that the Ecclesiastical Court decided that our twenty-one houses were held in full fee of the bishopric, and that we could only redeem this tribute by paying to his Reverence the Bishop two marks silver gilt of the value of six livres parisis. Now, I have not yet been able to collect these two marks, and you know it. -I know that I want money, repeated Jehan for the third time. -And what do you want it for? -This question brought a ray of hope to Jehans eyes. He assumed his coaxing, demure air once more. -Look you, dear brother Claude, I do not come to you with any bad intent. I do not purpose to squander your money in a tavern, or ruffle it through the streets of Paris in gold brocade and with my lackey behind me cum meo laquasio. No, brother, tis for a good work. -What good work? asked Claude, somewhat surprised. -Why, two of my friends wish to purchase some swaddling-clothes for the infant of a poor widow of the Haudriette Convent. Tis a charity. It will cost three florins, and I would like to add my contribution. -Who are your two friends? -Pierre lAssommeur4 and Baptiste Croque-Oison.5 -Humph! said the Archdeacon; these are names that go as fitly with a good work as a bombard upon a high altar. -It cannot be denied that Jehan had not been happy in the choice of names for his two friends. He felt it when it was too late. -Besides, continued the shrewd Claude, what sort of swaddling-clothes are they which cost three florins and for the infant of a Haudriette? Since when, pray, do the Haudriette widows have babes in swaddling-clothes? -Jehan broke the ice definitely. -Well, then, I want some money to go and see Isabeau la Thierrye this evening at the Val-d Amour! -Vile profligate! cried the priest. -Anagneia, retorted Jehan. -This quotation, selected by the boy no doubt in sheer malice from those on the wall of the cell, produced a singular effect upon the priest. He bit his lip, and his anger was lost in his confusion. -Get you gone! said he to Jehan; I am expecting some one. -The scholar made one last attempt. -Brother Claude, give me at least one little parisis to get some food. -How far have you advanced in the Decretals of Gratian? asked Dom Claude. -I have lost my note-books. -Where are you in Latin classics? -Somebody stole my copy of Horatius. -And where in Aristotle? -Faith, brother! what Father of the Church is it who says that the errors of heretics have ever found shelter among the thickets of Aristotles metaphysics? A straw for Aristotle! I will never mangle my religion on his metaphysics. -Young man, replied the Archdeacon, at the last entry of the King into Paris, there was a gentleman named Philippe de Comines, who displayed embroidered on his saddle-cloth this motto which I counsel you to ponder well: Qui non laborat non manducet. 6 -The scholar stood a moment silent, his eyes bent on the ground, his countenance chagrined. Suddenly he turned towards Claude with the quick motion of a wagtail. -So, good brother, you refuse me even a sou to buy a crust of bread? -Qui non laborat non manducet. -At this inflexible answer Jehan buried his face in his hands, like a woman sobbing, and cried in a voice of despair: -Otototototoi! -What do you mean by this, sir? demanded Claude, taken aback at this freak. -Well, what? said the scholar, raising a pair of impudent eyes into which he had been thrusting his fists to make them appear red with tears; its Greek! it is an anap?st of ?schylus admirably expressive of grief. And he burst into a fit of laughter so infectious and uncontrolled that the Archdeacon could not refrain from smiling. After all, it was Claudes own fault: why had he so spoiled the lad? -Oh, dear brother Claude, Jehan went on, emboldened by this smile, look at my broken shoes. Is there a more tragic buskin in the world than a boot that gapes thus and puts out its tongue? -The Archdeacon had promptly resumed his former severity. -I will send you new shoes, but no money. -Only one little parisis, brother, persisted the suppliant Jehan. I will learn Gratian by heart, I am perfectly ready to believe in God, I will be a very Pythagoras of science and virtue. But one little parisis, for pitys sake! Would you have me devoured by famine, which stands staring me in the face with open maw, blacker, deeper, more noisome than Tartarus or a monks nose ? -Dom Claude shook his head Qui non laborat -Jehan did not let him finish. Well! he cried, to the devil, then! Huzza! Ill live in the taverns, Ill fight, Ill break heads and wine cups, Ill visit the lasses and go to the devil! -And so saying, he flung his cap against the wall and snapped his fingers like castanets. -The Archdeacon regarded him gravely. Jehan, said he, you have no soul. -In that case, according to Epicurus, I lack an unknown something made of another something without a name. -Jehan, you must think seriously of amending your ways. -Ah ?a! cried the scholar, looking from his brother to the alembics on the furnaces, everything seems awry here tempers as well as bottles! -Jehan, you are on a slippery downward path. Know you whither you are going? -To the tavern, answered Jehan promptly. -The tavern leads to the pillory. -Tis as good a lantern as any other, and one, may-be, with which Diogenes would have found his man. -The pillory leads to the gibbet. -The gibbet is a balance with a man at one end and the whole world at the other. It is good to be the man. -The gibbet leads to hell. -Thats a good big fire. -Jehan, Jehan! all this will have a bad end! -It will have had a good beginning. -At this moment there was a sound of footsteps on the stairs. -Silence! said the Archdeacon, his finger on his lips, here is Ma?tre Jacques. Hark you, Jehan, he added in a low voice, beware of ever breathing a word of what you have seen or heard here. Hide yourself quickly under this furnace, and do not make a sound. -The scholar was creeping under the furnace when a happy thought struck him. -Brother Claude, a florin for keeping still! -Silence! I promise it you! -No, give it me now. -Take it, then! said the Archdeacon, flinging him his whole pouch angrily. Jehan crept under the furnace, and the door opened. -______________________ -1 Blow, hope. -2 Whence, whither?Man is a monster unto men.The stars, a fortress.The name, a wonder.A great book, a great evil.Dare to be wise.It bloweth where it listeth. -3 Account the Lord of heaven thy ruler upon earth. -4 The slaughterer. -5 The rook. -6 He who will not work shall not eat. -Chapter 5 - The Two Men in Black -The person who entered wore a black gown and a morose air. What at the first glance struck our friend Jehan (who, as may be supposed, so placed himself in his retreat as to be able to see and hear all at his ease) was the utter dejection manifest both in the garments and the countenance of the new-comer. There was, however, a certain meekness diffused over that face; but it was the meekness of a cat or of a judge a hypocritical gentleness. He was very gray and wrinkled, about sixty, with blinking eye-lids, white eye-brows, a pendulous lip, and large hands. When Jehan saw that it was nothing more that is to say, merely some physician or magistrate, and that the mans nose was a long way from his mouth, a sure sign of stupidity he ensconced himself deeper in his hole, desperate at being forced to pass an indefinite time in such an uncomfortable posture and such dull company. -The Archdeacon had not even risen to greet this person. He motioned him to a stool near the door, and after a few moments silence, during which he seemed to be pursuing some previous meditation, he remarked in a patronizing tone: -Good-day to you, Ma?tre Jacques. -And to you greeting, Ma?tre, responded the man in black. -There was between these two greetings the offhand Ma?tre Jacques of the one, and the obsequious Ma?tre of the other the difference between Sir and Your Lordship, of domne and domine. It was evidently the meeting between master and disciple. -Well, said the Archdeacon, after another interval of silence which Ma?tre Jacques took care not to break, will you succeed? -Alas, master, replied the other with a mournful smile, I use the bellows assiduously cinders and to spare but not a spark of gold. -Dom Claude made a gesture of impatience. That is not what I allude to, Ma?tre Jacques Charmolue, but to the charge against your sorcerer Marc Cenaine, you call him, I think butler to the Court of Accounts. Did he confess his wizardry when you put him to the question? -Alas, no, replied Ma?tre Jacques, with his deprecating smile. We have not that consolation. The man is a perfect stone. We might boil him in the pig-market, and we should get no word out of him. However, we spare no pains to arrive at the truth. Every joint is already dislocated on the rack; we have put all our irons in the fire, as the old comic writer Plautus has it: -Advorsum stimulos, laminas, crucesque, compedesque, Nervos, catenas, carceres, numellas, pedicas, boias. -But all to no purpose. That man is terrible. Tis loves labour lost! -You have found nothing fresh in his house? -Oh, yes, said Ma?tre Jacques, fumbling in his pouch, this parchment. There are words on it that we do not understand. And yet, monsieur, the criminal advocate, Philippe Lheulier, knows a little Hebrew, which he learned in an affair with the Jews of the Rue Kantersten, at Brussels. So saying, Ma?tre Jacques unrolled a parchment. -Give it to me, said the Archdeacon. Magic pure and simple, Ma?tre Jacques! he cried, as he cast his eyes over the scroll. Emen-Htan! that is the cry of the ghouls when they arrive at the witches Sabbath. Per ipsum, et cum ipso, et in ipso! that is the conjuration which rebinds the devil in hell. Hax, pax, max! that refers to medicine a spell against the bite of a mad dog. Ma?tre Jacques, you are Kings attorney in the Ecclesiastical Court; this parchment is an abomination. -We will put him again to the question. Then here is something else, added Ma?tre Jacques, fumbling once more in his bag, which we found at Marc Cenaines. -It was a vessel of the same family as those which encumbered the furnace of Dom Claude. Ah, said the Archdeacon, an alchemists crucible. -I dont mind confessing to you, Ma?tre Jacques went on, with his timid and constrained smile, that I have tried it over the furnace, but succeeded no better than with my own. -The Archdeacon examined the vessel. What has he inscribed on his crucible? Och! och! the word for driving away fleas? Your Marc Cenaine is an ignoramus! I can well believe that you could not make gold with this! It will be useful to put in your sleeping alcove in the summer, but for nothing more. -Since we are on the subject of errors, said the Kings attorney, before coming up I was studying the doorway down below; is your Reverence quite sure that the beginnings of Natures workings are represented there on the side towards the H?tel-Dieu, and that among the seven naked figures at the feet of Our Lady, that with wings to his heels is Mercurius? -Yes, answered the priest; so Augustin Nypho writes that Italian doctor who had a bearded familiar which taught him everything. But we will go down, and I will explain it to you from the text. -Thank you, master, said Charmolue, bending to the ground. By-the-bye, I had forgotten! When do you wish me to arrest the little witch? -What witch? -That gipsy girl, you know, who comes and dances every day in the Parvis, in defiance of the prohibition. She has a familiar spirit in the shape of a goat with devils horns it can read and write and do arithmetic enough to hang all Bohemia. The charge is quite ready and would soon be drawn up. A pretty creature, on my soul, that dancing girl! the finest black eyes in the world two Egyptian carbuncles. When shall we begin? -The Archdeacon had grown deadly pale. -I will let you know, he stammered in almost inaudible tones, then added with an effort: Attend you to Marc Cenaine. -Never fear, answered Charmolue smiling. As soon as I get back he shall be strapped down again to the leather bed. But it is a very devil of a man. He tires out Pierrat Torterue himself, who has larger hands than I. As says our good Plautus -Nudus vinctus, centum pondo, es quando pendes per pedes.1 -The screw that is our most effectual instrument we shall try that. -Dom Claude seemed sunk in gloomy abstraction. He now turned to Charmolue. Ma?tre Pierrat Ma?tre Jacques, I should say look to Marc Cenaine. -Yes, yes, Dom Claude. Poor man! he will have suffered like Mummol. But what a thing to do to visit the witches Sabbath! and he butler to the Court of Accounts, who must know Charlemagnes regulation: Stryga vel masca. 2As to the little girl Smelarda, as they call her I shall await your orders. Ah! as we pass through the door you will explain to me also the signification of that gardener painted on the wall just as you enter the church. Is that not the Sower? H! master, what are you thinking about? -Dom Claude, fathoms deep in his own thoughts, was not listening to him. Charmolue, following the direction of his eyes, saw that they were fixed blankly on the spiders web which curtained the little window. At this moment a foolish fly, courting the March sunshine, threw itself against the net, and was caught fast. Warned by the shaking of his web, the enormous spider darted out of his central cell, and with one bound rushed upon the fly, promptly doubled it up, and with its horrible sucker began scooping out the victims head. Poor fly! said the Kings attorney, and lifted his hand to rescue it. The Archdeacon, as if starting out of his sleep, held back his arm with a convulsive clutch. -Ma?tre Jacques, he cried, let fate have its way! -Ma?tre Jacques turned round in alarm; he felt as if his arm were in an iron vice. The eye of the priest was fixed, haggard, glaring, and remained fascinated by the horrible scene between the spider and the fly. -Ah, yes! the priest went on, in a voice that seemed to issue from the depths of his being, there is a symbol of the whole story. She flies, she is joyous, she has but just entered life; she courts the spring, the open air, freedom; yes, but she strikes against the fatal web the spider darts out, the deadly spider! Hapless dancer! Poor, doomed fly! Ma?tre Jacques, let be it is fate! Alas! Claude, thou art the spider. But Claude, thou art also the fly! Thou didst wing thy flight towards knowledge, the light, the sun. Thy one care was to reach the pure air, the broad beams of truth eternal; but in hastening towards the dazzling loophole which opens on another world a world of brightness, of intelligence, of true knowledge infatuated fly! insensate sage! thou didst not see the cunning spiders web, by destiny suspended between the light and thee; thou didst hurl thyself against it, poor fool, and now thou dost struggle with crushed head and mangled wings between the iron claws of Fate! Ma?tre Jacques, let the spider work its will! -I do assure you, said Charmolue, who gazed at him in bewilderment, that I will not touch it. But in pity, master, loose my arm; you have a grip of iron. -The Archdeacon did not heed him. Oh, madman! he continued, without moving his eyes from the loophole. And even if thou couldst have broken through that formidable web with thy midges wings, thinkest thou to have attained the light! Alas! that glass beyond that transparent obstacle, that wall of crystal harder than brass, the barrier between all our philosophy and the truth how couldst thou have passed through that? Oh, vanity of human knowledge! how many sages have come fluttering from afar to dash their heads against thee! How many clashing systems buzz vainly about that everlasting barrier! -He was silent. These last ideas, by calling off his thoughts from himself to science, appeared to have calmed him, and Jacques Charmolue completely restored him to a sense of reality by saying: Come, master, when are you going to help me towards the making of gold? I long to succeed. -The Archdeacon shrugged his shoulders with a bitter smile. -Ma?tre Jacques, read Michael Pselluss Dialogus de Energia et Operatione Damonum. What we are doing is not quite innocent. -Speak lower, master! I have my doubts, said Charmolue. But one is forced to play the alchemist a little when one is but a poor attorney in the Ecclesiastical Court at thirty crowns tournois a year. Only let us speak low. -At this moment a sound of chewing and crunching from the direction of the furnace struck on the apprehensive ear of Ma?tre Jacques. -What is that? he asked. -It was the scholar, who, very dull and cramped in his hiding-place, had just discovered a stale crust and a corner of mouldy cheese, and had without more ado set to work upon both by way of breakfast and amusement. As he was very hungry, he made a great noise, giving full play to his teeth at every mouthful, and thus aroused the alarm of the Kings attorney. -It is my cat, the Archdeacon hastily replied; she must have got hold of a mouse in there. -This explanation entirely satisfied Charmolue. True, master, he said with an obsequious smile, all great philosophers have some familiar animal. You know what Servius says: Nullus enim locus sine genio est. 3 -Meanwhile, Dom Claude, fearing some new freak of Jehans, reminded his worthy disciple that they had the figures in the doorway to study together. They therefore quitted the cell, to the enormous relief of the scholar, who had begun to have serious fears that his chin would take root in his knees. -______________________ -1 Naked and bound thou weighest a hundred pounds when hung up by the feet. -2 A witch or ghost. -3 There is no place without its quardian spirit. -Chapter 6 - Of the Result of Launching a String of Seven Oaths in a Public Square -Te Deum laudamus! exclaimed Master Jehan, crawling out of his hole; the two old owls have gone at last. Och! och! Hax! pax! max! fleas! mad dogs! the devil! Ive had enough of their conversation. My head hums like a belfry. And mouldy cheese into the bargain! Well, cheer up! lets be off with the big brothers purse and convert all these coins into bottles. -He cast a look of fond admiration into the interior of the precious pouch, adjusted his dress, rubbed his shoes, dusted his shabby sleeves, which were white with ashes, whistled a tune, cut a lively step or two, looked about the cell to see if there was anything else worth taking, rummaged about the furnace and managed to collect a glass amulet or so by way of trinket to give to Isabeau la Thierrye, and finally opened the door, which his brother had left unfastened as a last indulgence, and which he in turn left open as a last piece of mischief, and descended the spiral staircase, hopping like a bird. In the thick darkness of the winding stairs he stumbled against something which moved out of the way with a growl. He surmised that it was Quasimodo, which circumstance so tickled his fancy that he descended the rest of the stairs holding his sides with laughter. He was still laughing when he issued out into the square. -He stamped his foot when he found himself on level ground. -Oh, most excellent and honourable pavement of Paris! he exclaimed. Oh, cursed staircase, that would wind the very angels of Jacobs ladder! What was I thinking of to go and thrust myself up that stone gimlet that pierces the sky, just to eat bearded cheese and look at the steeples of Paris through a hole in the wall! -He went on a few steps, and caught sight of the two owls lost in contemplation of the sculpture in the doorway. Approaching them softly on tip-toe, he heard the Archdeacon say in low tones to Charmolue: It was Guillaume of Paris who had the Job engraven on the lapis-lazuli coloured stone. Job represents the philosophers stone, which also must be tried and tormented in order to become perfect, as Raymond Lulle says: Sub conservatione form? specific? salva anima. 3 -Its all one to me, said Jehan; Ive got the purse. -At that moment he heard a powerful and ringing voice behind him give vent to a string of terrible oaths: -Sang-Dieu! Ventre-Dieu! B-Dieu! Corps de Dieu! Nombril de Belzbuth! Nom dun pape! Corne et tonnerre! -My soul on it! exclaimed Jehan, that can be no other than my friend Captain Ph?bus! -The name Ph?bus reached the ear of the Archdeacon just as he was explaining to the Kings attorney the meaning of the dragon hiding its tail in a caldron from which issued smoke and a kings head. Dom Claude started and broke off short to the great astonishment of Charmolue, then turned and saw his brother Jehan accosting a tall officer at the door of the Gondelaurier mansion. -It was, in fact, Captain Ph?bus Chateaupers. He was leaning his back against a corner of the house of his betrothed and swearing like a Turk. -My faith, Captain Ph?bus, said Jehan, taking his hand, but you are a wonderfully spirited swearer! -Thunder and devils! answered the captain. -Thunder and devils to you! retorted the scholar. -How now, my gentle captain, whence this overflow of elegant language? -Your pardon, friend Jehan! cried Ph?bus, shaking his hand, a runaway horse cant be pulled up short. Now I was swearing at full gallop. Ive just been with those mincing prudes, and by the time I come away my throats so full of oaths that I must spit them out, or by thunder I should choke! -Come and have a drink? asked the scholar. -This proposal calmed the young soldier. -With all my heart, but Ive no money. -But I have. -Nonsense! lets see. -With an air of good-natured superiority Jehan displayed the purse before his friends eyes. -Meanwhile the Archdeacon, leaving Charmolue standing gaping, had approached the two and stopped a few paces off, observing them without their noticing him, so absorbed were they in examining the contents of the purse. -A purse in your pocket, Jehan! exclaimed Ph?bus, why, tis the moon in a pail of water one sees it, but it is not there, it is only the reflection. Par Dieu! Ill wager its full of pebbles! -These are the pebbles with which I pave my breeches pockets, answered Jehan coldly; and without further wasting of words he emptied the purse on a corner-stone near by, with the air of a Roman saving his country. -As I live! muttered Ph?bus, targes! grands blancs! petits blancs! deniers parisis! and real eagle pieces! Tis enough to stagger one! -Jehan preserved his dignified and impassive air. A few liards had rolled into the mud; the captain in his enthusiasm stooped to pick them up. But Jehan restrained him. -Fie, Captain Ph?bus de Chateaupers! -Ph?bus counted the money, and turning solemnly to Jehan: Do you know, Jehan, said he, that there are twenty-three sous parisis here? Whom did you rob last night in the Rue Coupe-Gueule? -Jehan tossed his curly head. How if one has a brother, he said, narrowing his eyes as if in scorn, an archdeacon and a simpleton? -Corne de Dieu! cried Ph?bus, the worthy man! -Lets go and drink, said Jehan. -Where shall we go? said Ph?bus, to the Pomme dEve? -No, captain, lets go to the Vieille-Science. -A fig for your Vieille-Science, Jehan! the wine is better at the Pomme dEve; besides, theres a vine at the door that cheers me while I drink. -Very well, then here goes for Eve and her apple, said the scholar, taking Ph?bus by the arm. By-the-bye, my dear captain, you spoke just now of the Rue Coupe-Gueule.1 That is very grossly said; we are not so barbarous now we call it Rue Coupe-Gorge. 2 -The two friends turned their steps towards the Pomme dEve. Needless to say they first gathered up the money, and the Archdeacon followed them. -Followed them with a haggard and gloomy countenance. Was this the Ph?bus whose accursed name, since his interview with Gringoire, had mingled with his every thought? He did not know, but at any rate it was a Ph?bus, and this magic name was a sufficient magnet to draw the Archdeacon after the two thoughtless companions with stealthy step listening to all they said, anxiously attentive to their slightest gesture. For the rest, there was no difficulty in hearing all they had to say, so loudly did they talk, so little did they hesitate to let the passer-by share their confidences. Their talk was of duels, women, wine, folly of all sorts. -As they turned a corner, the sound of a tambourine came to them from a neighbouring side street. Dom Claude heard the officer say to the scholar: -Thunder! lets quicken our pace! -Why, Ph?bus? -Im afraid the gipsy will see me. -What gipsy? -The girl with the goat. -Esmeralda? -Thats it, Jehan. I always forget her deuce of a name. Let us hurry past or she will recognise me, and I dont want the girl to accost me in the street. -Do you know her then, Ph?bus? -At first, the Archdeacon saw Ph?bus lean over with a grin and whisper something in Jehans ear. Ph?bus then burst out laughing, and threw up his head with a triumphant air. -In very truth? said Jehan. -Upon my soul! -To-night? -To-night. -Are you sure shell come? -But you must be mad, Jehan. Is there ever any doubt about these things? -Captain Ph?bus, you are a lucky warrior! -The Archdeacon overheard all this conversation. His teeth chattered. A visible shudder ran through his whole frame. He stopped a moment to lean against a post like a drunken man; then he followed the track of the two boon companions. -When he came up with them again they had changed the subject. They were singing at the top of their voices the refrain of an old song: -The lads, the dice who merrily throw, -Merrily to the gallows go. -______________________ -1 Cut-weasand. -2 Cut-throat. -3 By preserving it under a special form the soul is saved. -Chapter 7 - The Spectre-Monk -The far-famed cabaret of the Pomme dEve was situated in the University, at the corner of the Rue de la Rondelle and the Rue du Batonnier. It consisted of one spacious room on the ground floor, the central arch of its very low ceiling supported by a heavy wooden pillar painted yellow. There were tables all round, shining pewter pots hanging on the walls, a constant crowd of drinkers, and girls in abundance. A single window looked on to the street; there was a vine at the door, and over the door a creaking sheet of iron having a woman and an apple painted on it, rusted by the rain and swinging in the wind this was the sign-board. -Night was falling; the street was pitch-dark, and the cabaret, blazing with candles, flared from afar like a forge in the gloom, while through the broken window-panes came a continuous uproar of clinking glasses, feasting, oaths, and quarrels. Through the mist which the heat of the room diffused over the glass of the door a confused swarm of figures could be seen, and now and then came a roar of laughter. The people going to and fro upon their business hastened past this noisy casement with averted eyes. Only now and then some little ragamuffin would stand on tip-toe until he just reached the window-ledge, and shout into the cabaret the old jeering cry with which in those days they used to follow drunkards: Aux Houls, saouls, saouls, saouls! -One man, however, was pacing imperturbably backward and forward in forward in front of the noisy tavern, never taking his eye off it, nor going farther away from it than a sentry from his box. He was cloaked to the eyes, which cloak he had just purchased at a clothiers shop near the Pomme dEve, perhaps to shield himself from the keen wind of a March night, perhaps also to conceal his dress. From time to time he stopped before the dim latticed casement, listening, peering in, stamping his feet. -At length the door of the cabaret opened this was evidently what he had been waiting for and a pair of boon companions came out. The gleam of light that streamed out of the doorway glowed for a moment on their flushed and jovial faces. The man in the cloak went and put himself on the watch again under a porch on the opposite side of the street. -Corne et tonnerre! said one of the two carousers. Its on the stroke of seven the hour of my rendezvous. -I tell you, said his companion, speaking thickly, I dont live in the Rue des Mauvaises-Paroles indignus qui inter mala verba habitat. My lodging is in the Rue Jean-Pain-Mollet in vico Johannis-Pain-Mollet, and youre more horny than a unicorn if you say the contrary. Everybody knows that he who once rides on a bears back never knows fear again; but youve a nose for smelling out a dainty piece like Saint-Jacques de lH?pital! -Jehan, my friend, youre drunk, said the other. -His friend replied with a lurch. It pleases you to say so, Ph?bus; but it is proved that Plato had the profile of a hound. -Doubtless the reader has already recognised our two worthy friends, the captain and the scholar. It seems that the man who was watching them in the dark had recognised them too, for he followed slowly all the zigzags which the scholar obliged the captain to make, who, being a more seasoned toper, had retained his self-possession. Listening intently to them, the man in the cloak overheard the whole of the following interesting conversation : -Corbacque! Try to walk straight, sir bachelor. You know that I must leave you anon. It is seven oclock, and I have an appointment with a woman. -Leave me then! I see stars and spears of fire. Youre like the Chateau of Dampmartin that burst with laughter. -By the warts of my grandmother! Jehan, thats talking nonsense with a vengeance! Look you, Jehan, have you no money left? -Monsieur the Rector, it is without a mistake : the little slaughter-house parva boucheria! -Jehan! friend Jehan! you know I promised to meet that girl at the end of the Saint-Michel bridge; that I can take her nowhere but to La Falourdels, and that I must pay for the room. The old white-whiskered jade wont give me credit. Jehan, I beseech you! Have we drunk the whole contents of the curs pouch? -The consciousness of having employed the other hours well is a right and savoury condiment to our table. -Liver and spleen! a truce to your gibberish! Tell me, little limb of the devil, have you any money left? Give it me, or, by Heaven, Ill search you though you were as leprous as Job and as scabby as C?sar! -Sir, the Rule Galiache is a street which has the Rue de la Verrerie at one end and the Rue de la Tixanderie at the other. -Yes, yes, my good friend Jehan my poor boy the Rue Galiache yes, youre right, quite right. But for the love of Heaven collect yourself! I want but one sou parisis, and seven oclock is the hour. -Silence all round and join in the chorus: - When the rats have every cat devoured, -The king shall of Arras be the lord; -When the sea, so deep and wide, -Shall be frozen over at midsummertide, -Then out upon the ice youll see -How the men of Arras their town shall flee. -Well, scholar of Antichrist, the foul fiend strangle thee! cried Ph?bus, roughly pushing the tipsy scholar, who reeled against the wall and slid gently down upon the pavement of Philippe Augustus. Out of that remnant of fraternal sympathy which never wholly deserts the heart of a bottle companion, Ph?bus with his foot rolled Jehan to one of those pillows of the poor which Heaven provides at every street corner of Paris, and which the rich scornfully stigmatize with the name of rubbish-heap. The captain propped Jehans head upon an inclined plane of cabbage-stumps, and forthwith the scholar struck up a magnificent tenor snore. However, the captain still entertained some slight grudge against him. So much the worse for thee if the dust-cart come and shovel thee up in passing, said he to the poor, slumbering student; and he went on his way. -The man with the cloak, who still dogged his footsteps, halted a moment as if struggling with some resolve; then, heaving a deep sigh, he went on after the soldier. -Like them, we will leave Jehan sleeping under the friendly eye of heaven, and, with the readers permission, follow their steps. -On turning into the Rue Saint-Andr-des-Arcs, Captain Ph?bus perceived that some one was following him. Happening to glance behind him, he saw a sort of shade creeping after him along the wall. He stopped; he went on, the shade also moved forward. However, it caused him but little uneasiness. Ah, bah! he said to himself, I havent a sou on me. -In front of the College dAutun he made a halt. It was here that he had shuffled through what he was pleased to call his studies, and from a naughty school-boy habit which still clung to him he never passed the College without offering to the statue of Cardinal Pierre Bertram, which stood to the right of the entrance, that kind of affront of which Priapus complains so bitterly in Horaces satire: Olim truncus eram ficulnus. He therefore paused as usual at the effigy of the cardinal. The street was perfectly empty. As he was preparing to proceed on his way, he saw the shadow approaching him slowly; so slowly that he had the leisure to observe that it wore a cloak and a hat. Arrived at his side, it stopped and stood as motionless as the statue of the cardinal; but it fixed on Ph?bus a pair of piercing eyes which gleamed with the strange light that the pupils of a cat give forth at night. -The captain was no coward, and would have cared very little for a robber rapier in hand; but this walking statue, this petrified man, froze his blood. Queer stories were going about at that time of a spectre-monk who nightly roamed the streets of Paris, and these stories now returned confusedly to his mind. He stood for a moment bewildered and stupefied, and then broke the silence. -Sir, said he, forcing a laugh, if you are a thief, which I trust is the case, you look to me for all the world like a heron attacking a nutshell. My good fellow, I am a ruined youth of family. But try your luck here in the chapel of this College you will find a piece of the true cross set in silver. -The hand of the shade came forth from under its cloak and fell upon Ph?buss arm with the grip of an eagles talons, while at the same time it spoke. Captain Ph?bus de Chateaupers! it said. -The devil! exclaimed Ph?bus; you know my name? -I know more than your name, returned the cloaked man in sepulchral tones. I know that you have a rendezvous to-night. -Yes, I have, answered Ph?bus in amazement. -At seven oclock. -In a quarter of an hour. -At La Falourdels. -Precisely. -The old procuress of the Pont Saint-Michel. -Of Saint-Michael the Archangel, as says the paternoster. -Impious one! growled the spectre. With a woman? -Confiteor I confess it. -Whose name is -La Smeralda, said Ph?bus lightly; all his carelessness returned to him. -At this name the spectres grip tightened, and he shook the captains arm furiously. -Captain Ph?bus de Chateaupers, thou liest! -Any one beholding at that moment the flame of anger that rushed to the soldiers face, his recoil so violent that it relieved him from the others clutch, the haughty air with which he laid his hand upon the hilt of his sword, and, in face of that passionate resentment, the sullen immobility of the man in the cloak any one beholding this would have been startled. It was like the combat between Don Juan and the statue. -Christ and Satan! cried the captain, thats a word that seldom attacks the ear of a Chateaupers! Thou darest not repeat it! -Thou liest! said the shade coldly. -The captain ground his teeth. Spectre-monk, phantom, superstitions all were forgotten at this moment. He saw only a man and an insult. -Ha very good! he stammered, his voice choking with rage, and he drew his sword, still stammering for passion makes a man tremble as well as fear. Draw, he cried, here on the spot draw and defend yourself! There shall be blood upon these stones! -The other never stirred. Then, as he saw his adversary on guard and ready to run him through Captain Ph?bus, said he, and his voice shook with bitterness, you are forgetting your assignation. -The angry fits of such men as Ph?bus are like boiling milk of which a drop of cold water will stay the ebullition. These few words brought down the point of the sword which glittered in the captains hand. -Captain, continued the man, to-morrow the day after a month ten years hence you will find me ready to cut your throat; but now go to your rendezvous. -Why, in truth, said Ph?bus, as if parleying with himself, a sword and a girl are two charming things with which to have a rendezvous; but I see no reason why I should miss the one for the sake of the other, when I can have them both. And with that he put up his sword. -Go to your rendezvous, repeated the unknown. -Sir, said Ph?bus with some embarrassment, thanks for your courtesy. You are right, there will be plenty of time to-morrow for us to mutually make slashes and buttonholes in father Adams doublet. I am obliged to you for thus permitting me to pass another agreeable quarter of an hour. I was indeed in hopes of laying you in the gutter, and yet arriving in time for the lady, all the more that it is not amiss to make women wait for you a little on such occasions. But you seem to be a fellow of mettle, so it will be safer to put it off till to-morrow. So now I will be off to my rendezvous; it is for seven oclock, you know. Here Ph?bus scratched his ear. Ah, corne Dieu! Id forgotten I have not a sou to pay the hire of the garret, and the old hag will want to be paid in advance she will not trust me. -Here is the wherewithal to pay. -Ph?bus felt the cold hand of the unknown slip a large coin into his. He could not refrain from accepting the money and grasping the hand. -Gods truth! he exclaimed, but you are a good fellow! -One condition, said the man; prove to me that I was wrong, and that you spoke the truth. Hide me in some corner whence I may see whether this woman be really she whom you named. -Oh, answered Ph?bus, I have not the slightest objection. We shall use the Sainte-Marthe room, and you can see into it as much as you like from a little den at one side of it. -Come, then, said the shade. -At your service, said the captain. For all I know, you may be Messer Diabolus in person. But lets be good friends to-night; to-morrow I will pay you all my debts both of the purse and the sword. -They went forward at a rapid pace, and in a few moments the sound of the river below told them that they were on the Pont Saint-Michel, at that time lined with houses. -I will get you in first, said Ph?bus to his companion, and then go and fetch the lady, who was to wait for me near the Petit-Chatelet. -His companion made no reply. Since they had been walking side by side he had not uttered a word. Ph?bus stopped in front of a low door and knocked loudly. A light shone through the crevices of the door. -Whos there? cried a quavering old voice. -Corps-Dieu! Tte-Dieu! Ventre-Dieu! answered the captain. -The door opened on the instant, revealing to the newcomers an old woman and an old lamp, both of them trembling. The old woman was bent double, clothed in rags, her palsied head, out of which peered two little blinking eyes, tied up in a kerchief, and wrinkles everywhere her hands, her face, her neck; her lips were fallen in over her gums, and all round her mouth were tufts of white bristles, giving her the whiskered look of a cat. -The interior of the hovel was no less dilapidated than herself the plaster dropping from the walls, smoke-blackened beams, a dismantled chimney-piece, cobwebs in every corner; in the middle a tottering company of broken-legged tables and stools, in the cinders a dirty child, and at the back a stair-case, or rather a wooden ladder, leading to a trap-door in the ceiling. -As he entered this den, Ph?buss mysterious companion pulled his cloak up to his eyes. Meanwhile the captain, swearing like a Saracen, hastened to produce his crown piece. -The Sainte-Marthe room, he said as he presented it. -The old hag treated him like a lord and shut up the cu in a drawer. It was the coin Ph?bus had received from the man in the cloak. No sooner was her back turned, than the little tousle-headed ragamuffin playing in the cinders stole to the drawer, adroitly abstracted the coin, and replaced it by a withered leaf which he plucked from a fagot. -The old woman signed to the two gentlemen, as she entitled them, to follow her, and ascended the ladder. Arrived on the upper floor she set down her lamp upon a chest, and Ph?bus, as one knowing the ways of the house, opened a side door giving access to a small dark space. -In here, my dear fellow, said he to his companion. The man in the cloak obeyed without a word. The door closed behind him; he heard Ph?bus bolt it, and a moment afterward return down the ladder with the old woman. The light had disappeared. -Chapter 8 - The Convenience of Windows Overlooking the River -Claude Frollo for we presume the reader, more intelligent than Ph?bus, has seen throughout this adventure no other spectre-monk than the Archdeacon Claude Frollo groped about him for some moments in the darksome hole into which the captain had thrust him. It was one of those corners which builders sometimes reserve in the angle between the roof and the supporting wall. The vertical section of this den, as Ph?bus had very aptly termed it, would have exhibited a triangle. It had no window of any description, and the slope of the roof prevented one standing upright in it. Claude, therefore, was forced to crouch in the dust and the plaster that cracked under him. His head was burning. Groping about him on the floor, he found a piece of broken glass which he pressed to his forehead, and so found some slight relief from its coldness. -What was passing at that moment in the dark soul of the Archdeacon? God and himself alone knew. -According to what fatal order was he disposing in his thoughts La Esmeralda, Ph?bus, Jacques Charmolue, his fondly loved young brother, abandoned by him in the gutter, his cloth, his reputation perhaps, dragged thus into the house of the notorious old procuress all these images these wild doings? I cannot say; but it is very certain that they formed a horrible group in his minds eye. -He had been waiting a quarter of an hour, and he felt that he had aged a century in that time. Suddenly he heard the wooden ladder creak. Some one was ascending it. The trap-door opened again, and once more the light made its appearance. In the worm-eaten door of his retreat there was a crack; to this he pressed his face and could thus see all that went on in the adjoining space. The old cat-faced hag came first through the trap-door, lamp in hand; then followed Ph?bus, twirling his mustaches; and lastly a third person, a beautiful and graceful figure La Esmeralda. To the priest she issued from below like a dazzling apparition. Claude shook, a mist spread before his eyes, his pulses throbbed violently, everything turned round him, there was a roaring in his ears; he saw and heard no more. -When he came to himself again, Ph?bus and Esmeralda were alone, seated upon the wooden chest beside the lamp, the light of which revealed to the Archdeacon the two youthful figures and a miserable pallet at the back of the attic. -Close to the couch was a window, the casement of which, cracked and bulging like a spiders web in the rain, showed through its broken strands a small patch of sky, and far down it the moon reclining on a pillow of soft clouds. -The girl was blushing, panting, confused. Her long, drooping lashes shaded her glowing cheeks. The officer, to whom she dared not lift her eyes, was radiant. Mechanically, and with a ravishing coy air, she was tracing incoherent lines on the bench with the tip of her finger, her eyes following the movement. Her foot was hidden, for the little goat was lying on it. -The captain was arrayed for conquest, with ruffles of gold lace at his throat and wrists the extreme of elegance in those days. -It was not without difficulty that Dom Claude could hear their conversation, so loudly did the blood beat in his ears. -A dull affair enough, the conversation of a pair of lovers one never-ending I love you; a musical phrase, but terribly monotonous and insipid to the indifferent listener. But Claude was no indifferent listener. -Oh, said the girl, without lifting her eyes, do not despise me, Monseigneur Ph?bus. I feel that I am doing very wrong! -Despise you, pretty one! returned the officer with an air of superior and princely gallantry, despise you, Tte-Dieu, and what for? -For having followed you. -On that score, my charmer, we do not at all agree. I ought not to despise, but to hate you. -The girl looked up at him frightened. Hate me! What have I done? -Why, you have taken so much soliciting. -Alas! said she, it is that I am breaking a vow I shall never find my parents the amulet will lose its virtue but what of that? what need have I of a father or mother now? And she fixed on the soldier her large dark eyes, dewy with tenderness and delight. -The devil fly away with me if I know what you mean! cried Ph?bus. -Esmeralda was silent for a moment, then a tear rose to her eyes, and a sigh to her lips, as she murmured, Oh, sir, I love you! -There was around the girl such a halo of chastity, such a perfume of virtue, that Ph?bus was not quite at his ease with her. These words, however, emboldened him. You love me! he exclaimed with transport, and threw his arm round the gipsys waist. He had only been on the lookout for an opportunity. -The priest beheld this, and tried with his finger-tip the edge of the dagger which he kept concealed in his bosom. -Ph?bus, the gipsy went on, at the same time gently disengaging her waist from the officers clinging hands, you are good, you are generous, you are handsome. You saved me me, who am but a poor wandering gipsy girl. I had long dreamed of an officer who should save my life. It was of you I dreamed before I met you, my Ph?bus. The officer of my dream wore a fine uniform like yours, a grand look, a sword. You are called Ph?bus; it is a beautiful name. I love your name; I love your sword. Draw your sword, Ph?bus, and let me look at it. -Child! said the captain, unsheathing his sword with an indulgent smile. -The Egyptian looked at the hilt, at the blade, examined with adorable curiosity the monogram on the guard, and then kissed the sword. You are the sword of a brave man, she said. I love my officer. -Here Ph?bus availed himself of the opportunity, as she bent over the sword, to press a kiss upon her fair neck, which made the girl flush crimson and draw herself up, while the priest ground his teeth in the darkness. -Ph?bus, the gipsy resumed, let me talk to you. But first, pray you, walk about a little that I may see you at your full height, and hear the ring of your spurs. How handsome you are! -The captain rose to please her, chiding her the while with a smile of satisfied vanity. What a child it is! Apropos, sweetheart, have you ever seen me in gala uniform? -Alas! no, said she. -Ah, thats worth looking at! He reseated himself beside the gipsy, but much closer this time than before. Listen, my sweet -The gipsy girl gave two or three little taps of her pretty hand on his mouth with a playfulness that was full of child-like grace and gaiety. No, no, I will not listen to anything. Do you love me? I want you to tell me if you love me. -Do I love thee, angel of my life! exclaimed the captain, sinking on one knee before her. I am thine body, blood, and soul; all, all would I give for thee. I love thee, and have never loved but thee. -The captain had so often repeated this sentence, on so many similar occasions, that he delivered it at one breath, and without a single blunder. At this passionate declaration the Egyptian raised to the dingy ceiling which here took the place of heaven a look full of ineffable happiness. Oh, she murmured, this is the moment at which one should die! -Ph?bus found the moment more suitable for snatching another kiss, which went to torture the miserable Archdeacon in his hiding-place. -Die! cried the amorous captain. What are you saying, my angel? This is the time to live, or Jupiter is but a scoundrel! To die at the beginning of so delicious an occasion! Corne de b?uf that were a poor joke indeed! No, indeed. Listen, my dear Similar, Esmenarda Pardon me! but youve got a name so prodigiously Saracen that I cant get it out properly tis a thicket that always brings me up short. -Alas! said the poor girl, and I used to like the name for its singularity. But since it displeases you, I would I were called Goton. -Oh, tis not worth crying about, sweetheart! Its a name one must get accustomed to, thats all. Once I know it by heart, twill come readily enough. Listen, then, my Similar, I love you to distraction its positively miraculous how much I love you. I know a little girl who is bursting with rage over it. -Who is that? the gipsy broke in jealously. -What does it matter to us? answered Ph?bus. Do you love me? -Oh! said she. -Well, thats enough. You shall see how much I love you too. May the great demon Neptune stick me on his fork, if I dont make you the happiest creature living. Well have a pretty little lodging somewhere. My archers shall parade before your windows. They are all mounted, and cut out those of Captain Mignon completely. There are bill-men, cross-bowmen, and culverin-men. I will take you to the great musters of the Paris men-at-arms at the Grange de Rully. Thats a very magnificent sight. Eighty thousand men under arms thirty thousand in shining armour; the sixty-seven banners of the trade guilds; the standards of the Parliament, of the Chamber of Accounts, the Public Treasury, of the Workers in the Mint in short, a devilish fine show! Then Ill take you to see the lions at the Kings palace beasts of prey, you know women always like that. -For some minutes the girl, absorbed in her own happy thoughts, had been dreaming to the sound of his voice with-out attending to his words. -Oh, how happy you will be, continued the soldier, and at the same time gently unfastening the gipsys belt. -What are you doing? she said brusquely this forceful proceeding had roused her from her dreams. -Nothing, answered Ph?bus. I was only saying that you would have to put away all this mountebank, street-dancer costume when you are going to be with me. -To be with you, my Ph?bus, said the girl fondly, and she fell silent and dreamy again. -Emboldened by her gentleness the captain clasped his arm about her waist without her offering any resistance; he then began softly to unlace the pretty creatures bodice, and so disarranged her neckerchief, that from out of it the panting priest beheld the gipsys beautiful bare shoulder rise, round and dusky as the moon through a misty horizon. -The girl let Ph?bus work his will. She seemed unconscious of what he was doing. The captains eyes gleamed. Suddenly she turned to him. Ph?bus, she said with a look of boundless love, teach me your religion. -My religion! exclaimed the captain with a guffaw. Teach you my religion! Thunder and lightning! what do you want with my religion? -That we may be married, answered she. -A mingled look of surprise, disdain, unconcern, and licentious passion swept over the captains face. Ah, bah! said he, who talks of marriage? -The gipsy turned pale, and let her head droop sadly on her breast. -Sweetheart, went on Ph?bus fondly, what matters such foolery as marriage? Shall we be any less loving for not having gabbled some Latin in a priests shop? -And as he said this in his most insinuating tones, he drew still closer to the gipsy; his caressing arms had resumed their clasp about that slender, pliant waist; his eye kindled more and more, and everything proclaimed that Captain Ph?bus was obviously approaching one of those moments at which Jupiter himself behaves so foolishly that worthy old Homer is obliged to draw a cloud over the scene. -Dom Claude, however, saw everything. The door was merely of worm-eaten old puncheon ribs, and left between them ample passage for his vulture gaze. This dark-skinned, broad-shouldered priest, condemned hitherto to the austere chastity of the cloister, shivered and burned alternately at this night-scene of love and passion. The sight of this lovely, dishevelled girl in the arms of a young and ardent lover turned the blood in his veins to molten lead. He felt an extraordinary commotion within him; his eye penetrated with lascivious jealousy under all these unfastened clasps and laces. Any one seeing the wretched mans countenance pressed close against the worm-eaten bars would have taken it for the face of a tiger looking through his cage at some jackal devouring a gazelle. -By a sudden, rapid movement Ph?bus snatched the gipsys kerchief completely off her neck. The poor girl, who had sat pale and dreamy, started from her reverie. She brusquely tore herself away from the too enterprising young officer, and catching sight of her bare neck and shoulders, blushing, confused, and mute with shame, she crossed her beautiful arms over her bosom to hide it. But for the flame that burned in her cheeks, to see her thus standing, silent and motionless, with drooping eyes, you would have taken her for a statue of Modesty. -But this action of the captains had laid bare the mysterious amulet which she wore round her neck. -What is that? he asked, seizing this pretext for once more approaching the beautiful creature he had frightened away. -Do not touch it, she answered quickly, it is my protection. -Through it I shall find my parents again if I remain worthy of that. Oh, leave me, Monsieur le Capitaine! Mother! my poor mother! where art thou? Come to my aid! Have pity, Monsieur Ph?bus give me back my kerchief to cover my bosom. -But Ph?bus drew back coldly. Ah, mademoiselle, he said, I see very plainly that you do not love me! -Not love him! cried the poor unhappy child, clinging wildly to him and drawing him down to the seat beside her. I do not love thee, my Ph?bus? What words are these, cruel, to rend my heart! Oh, come take me! take all! do with me what thou wilt! I am thine. What matters the amulet! What is my mother to me now! Thou art father and mother to me now, since I love thee! Ph?bus, beloved, look at me see, tis I tis that poor little one whom thou wilt not spurn from thee, and who comes, who comes herself to seek thee. My soul, my life, myself all, all belong to thee, my captain. Well, so be it we will not marry, since it is not thy wish. Besides, what am I but a miserable child of the gutter, while thou, my Ph?bus, art a gentleman. A fine thing, truly! A dancing girl to espouse an officer! I was mad! No, Ph?bus, I will be thy paramour, thy toy, thy pleasure what thou wilt only something that belongs to thee for what else was I made? Soiled, despised, dishonoured, what care I? if only I be loved I shall be the proudest and happiest of women. And when I shall be old and ugly, when I am no longer worthy of your love, monseigneur, you will suffer me to serve you. Others will embroider scarfs for you I, the handmaid, will have care of them. You will let me polish your spurs, brush your doublet, and rub the dust from off your riding-boots will you not, Ph?bus? You will grant me so much? And meanwhile, take me I am thine only love me! We gipsies, that is all we ask love and the free air of heaven! -Speaking thus, she threw her arms round the soldiers neck and raised her eyes to his in fond entreaty, smiling through her tears. Her tender bosom was chafed by the woollen doublet and its rough embroidery as the fair, half-nude form clung to his breast. The captain, quite intoxicated, pressed his lips to those exquisite shoulders, and the girl, lying back in his arms, with half-closed eyes, glowed and trembled under his kisses. -Suddenly above the head of Ph?bus she beheld another head a livid, convulsed face with the look as of one of the damned, and beside that face a raised hand holding a dagger. It was the face and the hand of the priest. He had broken in the door and stood behind the pair. Ph?bus could not see him. The girl lay motionless, petrified and speechless with terror at the appalling apparition, like a dove that raises her head and catches the terrible keen eye of the hawk fixed upon her nest. -She was unable even to cry out. She saw the dagger descend upon Ph?bus and rise again, reeking. -Malediction! groaned the captain, and fell. -The girl swooned, but at the moment ere her eyes closed and she lost all consciousness, she seemed to feel a fiery pressure on her lips, a kiss more searing than the brand of the torturer. -When she came to her senses she found herself surrounded by the soldiers of the watch; the captain was being borne away bathed in his blood, the priest had vanished, the window at the back of the room overlooking the river was wide open; they picked up a cloak which they supposed to belong to the officer, and she heard them saying to one another: -It is a witch who has stabbed a captain. -BOOK VIII -Chapter 1 - The Crown Piece changed into A Withered Leaf -Gringoire and the whole Court of Miracles were in a state of mortal anxiety. For a whole long month nobody knew what had become of Esmeralda, which greatly distressed the Duke of Egypt and his friends the Vagabonds nor what had become of her goat, which doubled the distress of Gringoire. One evening the Egyptian had disappeared, and from that moment had given no sign of life. All searching and inquiries had been fruitless. Some malicious beggars declared that they had met her on the evening in question in the neighbourhood of the Pont Saint-Michel in company with an officer, but this husband la mode de Bohme was a most incredulous philosopher, and, besides, he knew better than any one to what extent his wife was still a maid. He had had an opportunity of judging how impregnable was the chastity resulting from the combined virtues of the amulet and the gipsys own feelings, and he had mathematically calculated the power of resistance of the last-mentioned factor. On that score, therefore, he was quite easy. -Consequently he was quite unable to account for this disappearance, which was a source of profound regret to him. He would have lost flesh over it had such a thing been possible. As it was, he had forgotten everything over this subject, even to his literary tastes, even to his great opus: Dc figuris regularibus et irregularibus, which he counted on getting printed as soon as he had any money. For he raved about printing ever since he had seen the Didascolon of Hugues de Saint-Victor printed with the famous types of Wendelin of Spires. -One day, as he was passing dejectedly before the Tournelle Criminelle, he observed a small crowd at one of the doors of the Palais de Justice. -What is going on? he asked of a young man who was coming out. -I do not know, sir, replied the young man. They say a woman is being tried for the murder of a soldier. As there would seem to be some witchcraft in the business, the Bishop and the Holy Office have interfered in the case, and my brother, who is Archdeacon of Josas, spends his whole time there. As it happened, I wished to speak with him, but I could not get near him for the crowd which annoys me very much, for I want money. -Alack, sir, said Gringoire, I would I had any to lend you, but though my breeches pockets are in holes, it is not from the weight of coin in them. -He did not venture to tell the youth that he knew his brother the Archdeacon, whom he had never visited since the scene in the church a neglect which smote his conscience. -The scholar went his way, and Gringoire proceeded to follow the crowd ascending the stairs to the court-room. To his mind, there was nothing equal to the spectacle of a trial for dissipating melancholy, the judges exhibiting, as a rule, such extremely diverting stupidity. The crowd with whom he mingled walked and elbowed one another in silence. After a protracted and uneventful pilgrimage through a long dark passage which wound through the Palais like the intestinal canal of the old edifice, he arrived at a low door opening into a court-room which his superior height enabled him to explore over the swaying heads of the multitude. -The hall was vast and shadowy, which made it appear still larger. The day was declining, the long pointed windows admitted only a few pale rays of light, which died out before they reached the vaulted ceiling, an enormous trellis-work of carved wood, the thousand figures of which seemed to stir confusedly in the gloom. Several candles were already lighted on the tables, and gleamed on the heads of the law clerks buried in bundles of documents. The lower end of the hall was occupied by the crowd; to right and left sat gowned lawyers at tables; at the other extremity upon a raised platform were a number of judges, the back rows plunged in darkness motionless and sinister figures. The walls were closely powdered with fleurs-de-lis, a great figure of Christ might be vaguely distinguished above the heads of the judges, and everywhere pikes and halberds, their points tipped with fire by the glimmering rays of the candles. -Sir, said Gringoire to one of his neighbours, who are all those persons yonder, ranged like prelates in council? -Sir, answered the man, those on the right are the Councillors of the High Court, and those on the left the Examining Councillors the ma?tres in black gowns, the messires in red ones. -And above them, there, continued Gringoire, who is the big, red-faced one sweating so profusely? -That is Monsieur the President. -And those sheepsheads behind him? Gringoire went on we know that he had no great love for the magistrature, owing, may-be, to the grudge he bore against the Palais de Justice ever since his dramatic misadventure. -Those are the lawyers of the Court of Appeal of the Royal Palace. -And that wild boar in front of them? -Is the Clerk of the Court of Parliaments. -And that crocodile to the right of him? -Ma?tre Philippe Lheulier, Kings advocate extraordinary. -And to the left, that big black cat? -Ma?tre Jacques Charmolue, procurator in the Ecclesiastical Court, with the members of the Holy Office. -And may I ask, sir, said Gringoire, what all these worthies are about? -They are trying some one. -Trying whom? I see no prisoner. -It is a woman, sir. You cannot see her. She has her back turned to us, and is hidden by the crowd. Look, she is over there where you see that group of partisans. -Who is the woman? asked Gringoire; do you know her name? -No, sir, I have but just arrived. I conclude, however, from the presence of the Office that there is some question of witchcraft in the matter. -Ah, ha! said our philosopher, so we shall have the pleasure of seeing these black gowns devouring human flesh! Well, it is a spectacle as good as any other. -Do you not think, sir, that Ma?tre Jacques Charmolue has a very kindly air? observed his neighbour. -Hum! responded Gringoire. I am somewhat distrustful of kindness that has such thin nostrils and sharp lips. -Here the bystanders imposed silence on the two talkers. An important deposition was being heard. -My lords, an old woman was saying, whose face and shape generally was so muffled in her garments that she looked like an animated heap of rags; my lords, the thing is as true as that I am La Falourdel, for forty years a householder on the Pont Saint-Michel, and paying regularly all rents and dues and ground taxes the door opposite to the house of Tassin-Caillart, the dyer, which is on the side looking up the river. A poor old woman now, a pretty girl once-a-days, my lords! Only a few days before, they said to me: La Falourdel, do not spin too much of an evening, the devil is fond of combing old womens distaffs with his horns. Tis certain that the spectre-monk who haunted the Temple last year is going about the city just now; take care, La Falourdel, that he does not knock at your door. I ask whos there. Some one swears. I open the door. Two men come in a man in black with a handsome officer. You could see nothing of the black man but his eyes two live coals all the rest hat and cloak. So they say to me: The Sainte-Marthe room that is my upper room, my lords, my best one, and they give me a crown. I shut the crown in a drawer, and says I: That will do to buy tripe to-morrow at the slaughter-house of La Gloriette. We go upstairs. Arrived at the upper room, as I turn my back a moment, the man in black disappears. This astonishes me somewhat. The officer, who was handsome and grand as a lord, comes down again with me. He leaves the house, but in about the time to spin a quarter of a skein he returns with a beautiful young girl a poppet who would have shone like a star had her locks been properly braided. Following her came a goat a great goat whether black or white I cant remember. This set me to thinking. The girl that does not concern me but the goat! I dont like those animals with their beards and horns its too like a man. Besides, that smells of witch-craft. However, I say nothing. I had the crown piece. That is only fair, is it not, my lord judge? So I show the captain and the girl into the upper room and leave them alone that is to say, with the goat. I go down and get to my spinning again. I must tell you that my house has a ground floor and an upper storey; the back looks out on to the river, as do all the houses on the bridge, and the groundfloor window and the window of the upper floor open on to the water. Well, as I was saying, I sat down again to my spinning. I dont know why, but I began thinking about the spectre-monk whom the goat had brought to my mind, and that the pretty girl was dressed very outlandish, when all at once I hear a cry overhead and something fall on the floor, and then the window opening. I run to mine, which is just underneath, and see a black mass drop into the water a phantom dressed like a priest. It was moonlight, so I saw it quite plainly. It swam away towards the city. Then, all of a tremble, I called the watch. The gentlemen of the guard came in, and at first, not knowing what was the matter, they made merry over it and began to beat me. I explained to them. We go upstairs, and what do we find? My unfortunate room swimming in blood, the captain stretched his whole length on the floor with a dagger in his neck, the girl making as if she were dead, and the goat in a fury. A pretty business, say I. Twill be a fortnights work to clean up these boards. It must be scraped a terrible job! They carried away the officer, poor young man, and the girl halfnaked. But stay the worst is to come. The next morning, when I went to take the crown to buy my tripe, I found a withered leaf in its place! -The old beldame ceased. A murmur of horror went round the place. That phantom, that goat all this savours of magic, said one of Gringoires neighbours. And that withered leaf, added another. There can be no doubt, went on a third, that its some witch who has commerce with the spectre-monk to plunder officers. Gringoire himself was not far from thinking this connection both probable and alarming. -Woman Falourdel, said the President with majesty, have you nothing further to declare to the court? -No, my lord, answered the woman, unless that in the report my house has been named a tumble-down and stinking hovel, which is insulting language. The houses on the bridge are not very handsome, because they swarm with people; but, nevertheless, the butchers live there, and they are wealthy men with handsome and careful wives. -The magistrate who reminded Gringoire of a crocodile now rose. Peace! said he. I would beg you gentlemen not to lose sight of the fact that a dagger was found on the accused. Woman Falourdel, have you brought with you the withered leaf into which the crown was transformed that the demon gave you? -Yes, my lord. I found it again. Here it is. -An usher handed the dead leaf to the crocodile, who, with a doleful shake of the head, passed it to the President, who sent it on to the procurator of the Ecclesiastical Court, so that it finally made the round of the hall. -Tis a beech leaf, said Ma?tre Jacques Charmolue, an additional proof of magic! -A councillor then took up the word. Witness, you say two men went up together in your house: the man in black whom you first saw disappear and then swimming in the Seine in priests habit, and the officer. Which of the two gave you the crown? -The hag reflected for a moment, then answered, It was the officer. -A murmur ran through the crowd. -Ah, thought Gringoire, that somewhat shakes my conviction. -But Ma?tre Philippe Lheulier again interposed. I would remind you, gentlemen, that in the deposition taken down at his beside the murdered officer, while stating that a vague suspicion had crossed his mind at the instant when the black man accosted him, that it might be the spectre-monk, added, that the phantom had eagerly urged him to go and meet the accused, and on his (the captains) observing that he was without money, had given him the crown which the said officer paid to La Falourdel. Thus the crown is a coin of hell. -This conclusive observation appeared to dissipate all doubts entertained by Gringoire or any other sceptics among the listeners. -Gentlemen, you have the documents in hand, added the advocate as he seated himself, you can consult the deposition of Ph?bus de Chateaupers. -At this name the accused started up. Her head was now above the crowd. Gringoire, aghast, recognised Esmeralda. -She was deadly pale; her hair, once so charmingly braided and spangled with sequins, fell about her in disorder; her lips were blue, her sunken eyes horrifying. Alas! -Ph?bus! she cried distraught, where is he? Oh, my lords, before you kill me, in mercy tell me if he yet lives! -Silence, woman! answered the President; that is not our concern. -Oh, in pity, tell me if he lives! she cried again, clasping her beautiful wasted hands; and her chains clanked as she moved. -Well, then, said the Kings advocate dryly, he is at the point of death. Does that satisfy you? -The wretched girl fell back in her seat, speechless, tearless, white as a waxen image. -The President leaned down to a man at his feet who wore a gilded cap and a black gown, a chain round his neck, and a wand in his hand. -Usher, bring in the second accused. -All eyes were turned towards a little door which opened, and to Gringoires great trepidation gave entrance to a pretty little goat with gilded horns and hoofs. The graceful creature stood a moment on the threshold stretching her neck exactly as if, poised on the summit of a rock, she had a vast expanse before her eyes. Suddenly she caught sight of the gipsy girl, and leaping over the table and the head of the clerk in two bounds, she was at her mistresss knee. She then crouched at Esmeraldas feet, begging for a word or a caress; but the prisoner remained motionless, even little Djali could not win a glance from her. -Why tis my ugly brute, said old Falourdel, and now I recognise them both perfectly! -An it please you, gentlemen, we will proceed to the interrogation of the goat. -This, in effect, was the second criminal. Nothing was more common in those days than a charge of witchcraft against an animal. For instance, in the Provostry account for 1466 there is a curious specification of the expenses of the action against Gillet Soulart and his sow, executed for their demerits at Corbeil. Everything is detailed the cost of the pit to put the sow into; the five hundred bundles of wood from the wharf of Morsant; the three pints of wine and the bread, the victims last meal, fraternally shared by the executioner; and even the eleven days custody and keep of the sow at eight deniers parisis per day. At times they went beyond animals. The capitularies of Charlemagne and Louis le Debonnaire impose severe penalties on fiery phantoms who had the assurance to appear in the air. -Meanwhile the procurator of the Ecclesiastical Court exclaimed, If the demon that possesses this goat, and which has resisted every exorcism, persist in his sorceries, if he terrify the court thereby, we forewarn him that we shall be constrained to proceed against him with the gibbet or the stake. -Gringoire broke out in a cold sweat. -Charmolue then took from the table the gipsys tambourine, and presenting it in a certain manner to the goat, he asked: What is the time of day? -The goat regarded him with a sagacious eye, lifted her gilded hoof, and struck seven strokes. It was in truth seven oclock. A thrill of horror ran through the crowd. -Gringoire could contain himself no longer. She will be her own ruin! he exclaimed aloud. You can see for yourself she has no knowledge of what she is doing. -Silence down there! cried the usher sharply. -Jacques Charmolue, by means of the same man?uvrings with the tambourine, made the goat perform several other tricks in connection with the date of the day, the month of the year, etc., which the reader has already witnessed. And by an optical illusion peculiar to judicial proceedings, these same spectators, who doubtless had often applauded Djalis innocent performances in the public streets, were terrified by them under the roof of the Palais de Justice. The goat was indisputably the devil. -It was much worse, however, when the procurator, having emptied on the floor a certain little leather bag full of movable letters hanging from Djalis neck, the goat was seen to separate from the scattered alphabet the letters of the fatal name Ph?bus. The magic of which the captain had been a victim seemed incontrovertibly proven; and, in the eyes of all, the gipsy girl, the charming dancer who had so often dazzled the passer-by with her exquisite grace, was nothing more nor less than a horrible witch. -As for her, she gave no sign of life. Neither Djalis pretty tricks nor the menaces of the lawyers, nor the stifled imprecations of the spectators nothing reached her apprehension any more. -At last, in order to rouse her, a sergeant had to shake her pitilessly by the arm, and the President solemnly raised his voice: -Girl, you are of the race of Bohemians, and given to sorcery. In company with your accomplice, the bewitched goat, also implicated in this charge, you did, on the night of the twenty-ninth of March last, in concert with the powers of darkness, and by the aid of charms and spells, wound and poniard a captain of the Kings archers, Ph?bus de Chateaupers by name. Do you persist in your denial? -Horrors! cried the girl, covering her face with her hands. My Ph?bus! Oh, this is hell! -Do you persist in your denial? repeated the President coldly. -Of course I deny it! she answered in terrible tones; and she rose to her feet and her eyes flashed. -Then how do you explain the facts laid to your charge? continued the President sternly. -I have already said, she answered brokenly, I do not know. It is a priest, a priest who is unknown to me; a devilish priest who persecutes me -There you have it, interrupted the judge; the spectre-monk. -Oh, my lords, have pity! I am but a poor girl -Of Egypt, said the judge. -Ma?tre Jacques Charmolue here interposed in his mildest tones: In view of the painful obstinacy of the accused, I demand that she be put to the question. -Accorded, said the President. -A shudder ran through the frame of the hapless girl. She rose, however, at the order of the partisan-bearers, and walked with a tolerably firm step, preceded by Charmolue and the priests of the Office and between two lines of halberds, towards a masked door, which suddenly opened and shut again upon her, seeming to the dejected Gringoire like a horrible maw swallowing her up. -After she had disappeared a plaintive bleat was heard. It was the little goat. -The sitting was suspended. A councillor having observed that the gentleman were fatigued, and that it would be a long time to wait till the torture was over, the President replied that a magistrate should be able to sacrifice himself to his duty. -The troublesome and vexatious jade, said an old judge, to force us to apply the question when we have not yet supped! -Chapter 2 - Sequel to the Crown Piece changed into A Withered Leaf -After ascending and descending several flights of steps leading to passages so dark that they were lighted by lamps at mid-day, Esmeralda, still surrounded by her lugubrious attendants, was thrust by the sergeants of the guard into a chamber of sinister aspect. This chamber, circular in form, occupied the ground floor in one of those great towers which, even in our day, pierces the layer of modern edifices with which the present Paris has covered the old. There were no windows to this vault; no other opening than the lowbrowed entrance, closed by an enormous iron door. Yet it did not want for light. A furnace was built into the thickness of the wall, and in it a great fire, which filled the vault with its crimson glow and entirely outshone a miserable candle flickering in a corner. The iron grating which closed the furnace being raised at that moment only showed, against the flaming orifice whose licking flames danced on the grim walls, the lower extremity of its bars like a row of sharp black teeth, giving the fire the appearance of a fire-breathing dragon of the ancient myths. By the light that streamed from it the prisoner beheld, ranged round the chamber, frightful instruments the use of which she did not understand. In the middle a leather mattress was stretched almost touching the ground, and over that hung a leather strap with a buckle, attached to a copper ring held in the mouth of a flat-nosed monster carved in the keystone of the vaulted roof. Iron pincers, tongs, great ploughshares were heaped inside the furnace and glowed red-hot upon the fire. The blood-red gleam of the fire only served to bring into view a confused mass of horrible objects. -This Tartarus was known simply as the Question Chamber. -Upon the bed sat with the utmost unconcern Pierrat Torterue, the official torturer. His assistants, two squarefaced gnomes in leathern aprons and linen breeches, were turning the irons in the fire. -The poor girl might call up all her courage as she would; on entering that chamber she was seized with horror. -The myrmidons of the law ranged themselves on one side, the priests of the Office on the other. A clerk, a table and writing materials were in a corner. -Ma?tre Jacques Charmolue approached the Egyptian with his blandest smile. -My dear child, said he, do you persist in your denial? -Yes, she answered in an expiring voice. -In that case, Charmolue went on, it will be our painful duty to question you more urgently than we would otherwise desire. Have the goodness to seat yourself on this bed. Ma?tre Pierrat, kindly make room for mademoiselle, and close the door. -Pierrat rose with a growl. If I shut the door, he muttered, my fire will go out. -Well, then, my good fellow, replied Charmolue, leave it open. -Meanwhile, Esmeralda had remained standing. This bed of leather, on which so many poor wretches had writhed in agony, filled her with affright. Terror froze her to the marrow: she stood bewildered, stupefied. At a sign from Charmolue, the two assistants laid hold on her and placed her on the bed. They did not hurt her; but at the mere touch of these men, at the touch of the bed, she felt all her blood rush to her heart. She cast a distraught look round the chamber. She imagined she saw all these monstrous instruments of torture which were, to the instruments of any kind she had hitherto seen, what bats, centipeds, and spiders are among birds and insects come moving towards her from all sides to crawl over her body and pinch and bite her. -Where is the physician? asked Charmolue. -Here, answered a black gown she had not observed before. -She shuddered. -Mademoiselle, resumed the fawning voice of the attorney of the Ecclesiastical Court, for the third time, do you persist in denying the facts of which you are accused? -This time she only bent her head in assent she was past speaking. -You persist? said Jacques Charmolue. Then, to my infinite regret, I must fulfil the duty of my office. -Monsieur the Kings Attorney, said Pierrat, with which shall we begin? -Charmolue hesitated a moment with the ambiguous grimace of a poet seeking a rhyme. With the boot, he said at last. -The unhappy creature felt herself so completely forsaken of God and man, that her head dropped upon her breast like a thing inert and without any power in itself. The torturer and the physician approached her together, while the two assistants began to search in their hideous collection. -At the clank of these terrible irons the wretched child started convulsively, like a poor dead frog galvanized to life. -Oh! she murmured, so low that no one heard her; oh, my Ph?bus! Then she sank again into her previous immobility and but her stony silence. The spectacle would have wrung any but the hearts of judges. It might have been some sin-stained soul being questioned by Satan at the flaming gate of hell. Could the miserable body on which this awful swarm of saws and wheels and pincers was preparing to fasten could it be this gentle, pure, and fragile creature? Poor grain of millet which human justice was sending to be ground by the grewsome mill-stones of torture! -And now the horny hands of Pierrat Torterues assistants had brutally uncovered that charming leg, that tiny foot, which had so often astonished the passers-by with their grace and beauty in the streets of Paris. -Tis a pity! growled even the torturer at the sight of the slender and delicate limbs. -Had the Archdeacon been present, he would certainly have recalled at this moment his allegory of the spider and the fly. -Now, through the mist that spread before her eyes, the unhappy girl perceived the boot being brought forward, saw her foot, encased between the iron-bound boards, disappear within the frightful apparatus. Terror restored her strength. Take it away! she cried vehemently, starting up all dishevelled: Mercy! -She sprang from the bed to throw herself at Charmolues feet, but her leg was held fast in the heavy block of oak and iron, and she sank over the boot like a bee with a leaden weight attached to its wing. -At a sign from Charmolue they replaced her on the bed, and two coarse hands fastened round her slender waist the leather strap hanging from the roof. -For the last time, do you confess to the facts of the charge? asked Charmolue with his imperturbable benignity. -I am innocent, was the answer. -Then, mademoiselle, how do you explain the circumstances brought against you? -Alas, my lord, I know not. -You deny them? -All! -Proceed, said Charmolue to Pierrat. -Pierrat turned the screw, the boot tightened, and the victim uttered one of those horrible screams which have no written equivalent in any human language. -Stop! said Charmolue to Pierrat. Do you confess? said he to the girl. -All, cried the wretched girl. I confess! I confess! Mercy! -She had overestimated her forces in braving the torture. Poor child! life had hitherto been so joyous, so pleasant, so sweet, the first pang of agony had overcome her! -Humanity obliges me to tell you, observed the Kings attorney, that in confessing, you have only death to look forward to. -I hope but for that! said she, and fell back again on the leather bed, a lifeless heap, hanging doubled over the strap buckled round her waist. -Hold up, my pretty! said Ma?tre Pierrat, raising her. You look like the golden sheep that hangs round the neck of Monsieur of Burgundy. -Jacques Charmolue raised his voice. Clerk, write this down. Gipsy girl, you confess your participation in the love-feasts, Sabbaths, and orgies of hell, in company with evil spirits, witches, and ghouls? Answer! -Yes, she breathed faintly. -You admit having seen the ram which Beelzebub causes to appear in the clouds as a signal for the Sabbath, and which is only visible to witches? -Yes. -You confess to having adored the heads of Bophomet, those abominable idols of the Templars? -Yes. -To having had familiar intercourse with the devil under the form of a pet goat, included in the prosecution? -Yes. -Finally, you admit and confess to having, on the night of the twenty-ninth of March last, with the assistance of the demon and of the phantom commonly called the spectre-monk, wounded and assassinated a captain named Ph?bus de Chateaupers? -She raised her glazed eyes to the magistrate and answered mechanically, without a quiver of emotion, Yes. It was evident that her whole being was crushed. -Take that down, said Charmolue to the clerk. Then, turning to the torturer, Let the prisoner be unbound and taken back to the court. -When the prisoner was unbooted, the procurator of the Ecclesiastical Court examined her foot, still paralyzed with pain. Come, said he, theres no great harm done. You cried out in time. You could still dance, ma belle! -And turning to the members of the Office At length, justice is enlightened! That is a great consolation, messieurs! Mademoiselle will bear witness that we have used all possible gentleness towards her. -Chapter 3 - End of the Crown Piece changed into A Withered Leaf -When, pale and limping, she re-entered the Court of Justice, she was greeted by a general murmur of pleasure arising on the part of the public from that feeling of satisfied impatience experienced at the theatre at the expiration of the last entr acte of a play, when the curtain rises and one knows that the end is about to begin; and on the part of the judges from the hope of soon getting their supper. The little goat, too, bleated with joy. She would have run to her mistress, but they had tied her to the bench. -Night had now completely fallen. The candles, which had not been increased in number, gave so little light that the walls of the court were no longer visible. Darkness enveloped every object in a kind of mist, through which the apathetic faces of the judges were barely distinguishable. Opposite to them, at the extremity of the long hall, they could just see a vague white point standing out against the murky background. It was the prisoner. -She had dragged herself to her place. When Charmolue had magisterially installed himself in his, he sat down, then rose and said, without allowing all too much of his satisfaction at his success to become apparent: The prisoner has confessed all. -Bohemian girl, said the President, you have confessed to all your acts of sorcery, of prostitution, and of assassination committed upon the person of Ph?bus de Chateaupers? -Her heart contracted. They could hear her sobbing through the darkness. What you will, she returned feebly, only make an end of me quickly! -Monsieur the Kings Attorney in the Ecclesiastical Court, the court is ready to hear your requisitions. -Ma?tre Charmolue drew forth an appalling document, and commenced reading with much gesticulation and the exaggerated emphasis of the Bar a Latin oration, in which all the evidences of the trial were set out in Ciceronian periphrases, flanked by citations from Plautus. We regret being unable to offer our readers this remarkable composition. The author delivered it with marvellous eloquence. He had not concluded the exordium before the perspiration was streaming from his brow and his eyes starting from his head. -Suddenly, in the very middle of a rounded period, he broke off short, and his countenance, usually mild enough not to say stupid, became absolutely terrible. -Sirs! he cried (this time in French, for it was not in the document), Satan is so profoundly involved in this affair, that behold him present at our councils and making a mock of the majesty of the law. Behold him! -So saying, he pointed to the goat, which, seeing Charmolue gesticulate, thought it the right and proper thing to do likewise, and seated on her haunches was mimicking to the best of her ability with her fore-feet and bearded head the impressive pantomime of the Kings Attorney in the Ecclesiastical Court. This, if you will remember, was one of her most engaging performances. -This incident this final proof produced a great effect. They bound the goats feet, and Charmolue resumed the thread of his eloquence. -It was long indeed, but the peroration was admirable. The last sentence ran thus let the reader add in imagination the raucous voice and broken-winded elocution of Ma?tre Charmolue: -Ideo, domini, coram stryga demonstrata, crimine patente, intentione criminis existente, in nomine sanct? ecclesi? Nostr?-Domin? Parisiensis, qu? est in saisina habendi omnimodam altam et bassam, justitiam in illa hac intemerata Civitatis insula, tenore pr?sentium declaramus nos requirere, primo, aliquandam pecuniariam indemnitatem; secundo, amendationem honorabilem ante portalium maximum Nostr?-Domin?, ecclesi? cathedralis; tertio, sententiam, in virtute cujus ista stryga cum sua capella, seu in trivio vulgariter dicto la Grve, seu in insula exeunte in fluvio Sequan?, juxta pointam jardini regalis, execut? sint.1 -He resumed his cap and sat down again. -Eheu! groaned Gringoire, overwhelmed with grief. Bassa latinitas.2 -Another man in a black gown now rose near the prisoner. It was her advocate. The fasting judges began to murmur. -Advocate, said the President, be brief. -Monsieur the President, replied the advocate, since the defendant has confessed the crime, I have but one word to say to these gentlemen. I bring to their notice the following passage of the Salic law: If a witch have devoured a man and be convicted of it, she shall pay a fine of eight thousand deniers, which makes two hundred sous of gold. Let the court condemn my client to the fine. -An abrogated clause, said the Kings Advocate Extraordinary. -Nego.3 -Put it to the vote! suggested a councillor; the crime is manifest, and it is late. -The votes were taken without leaving the court. The judges gave their votes without a moments hesitation they were in a hurry. One after another their heads were bared at the lugubrious question addressed to them in turn in a low voice by the President. The hapless prisoner seemed to be looking at them, but her glazed eyes no longer saw anything. -The clerk then began to write, and presently handed a long scroll of parchment to the President; after which the poor girl heard the people stirring, and an icy voice say: -Bohemian girl, on such a day as it shall please our lord the King to appoint, at the hour of noon, you shall be taken in a tumbrel, in your shift, barefoot, a rope round your neck, before the great door of Notre-Dame, there to do penance with a wax candle of two pounds weight in your hands; and from there you shall be taken to the Place-de-Grve, where you will be hanged and strangled on the town gibbet, and your goat likewise; and shall pay to the Office three lion-pieces of gold in reparation of the crimes, by you committed and confessed, of sorcery, magic, prostitution, and murder against the person of the Sieur Ph?bus de Chateaupers. And God have mercy on your soul! -Oh, tis a dream! she murmured, and she felt rude hands bearing her away. -______________________ -1 Therefore, gentlemen, the witchcraft being proved and the crime made manifest, as likewise the criminal intention, in the name of the holy church of Notre-Dame de Paris, which is seized of the right of all manner of justice high and low, within this inviolate island of the city, we declare by the tenor of these presents that we require, firstly, a pecuniary compensation; secondly, penance before the great portal of the cathedral church of Notre-Dame; thirdly, a sentence, by virtue of which this witch, together with her goat, shall either in the public square, commonly called La Grve, or in the island stretching out into the river Seine, adjacent to the point of the royal gardens, be executed. -2 Oh, the monks Latin! -3 I say No. -Chapter 4 - Lasciate Ogni Speranza -In the Middle Ages, when an edifice was complete there was almost as much of it under the ground as over it. Except it were built on piles, like Notre-Dame, a palace, a fortress, a church, had always a double foundation. In the cathedrals it formed in some sort a second cathedral subterranean, low-pitched, dark, mysterious blind and dumbunder the aisles of the building above, which were flooded with light and resonant day and night with the music of the organ or the bells. Sometimes it was a sepulchre. In the palaces and fortresses it was a prison or a sepulchre sometimes both together. These mighty masses of masonry, of which we have explained elsewhere the formation and growth, had not mere foundations, but more properly speaking roots branching out underground into chambers, passages, and stairways, the counterpart of those above. Thus the churches, palaces, and bastilles might be said to be sunk in the ground up to their middle. The vaults of an edifice formed another edifice, in which you descended instead of ascending, the subterranean storeys of which extended downward beneath the pile of exterior storeys, like those inverted forests and mountains mirrored in the waters of a lake beneath the forests and mountains of its shores. -At the Bastille Saint-Antoine, at the Palais de Justice, and at the Louvre, these subterranean edifices were prisons. The storeys of these prisons as they sank into the ground became even narrower and darker so many zones presenting, as by a graduated scale, deeper and deeper shades of horror. Dante could find nothing better for the construction of his Inferno. These dungeon funnels usually ended in a tub-shaped pit, in which Dante placed his Satan and society the criminal condemned to death. When once a miserable being was there interred, farewell to light, air, life ogni speranza he never issued forth again but to the gibbet or the stake unless, indeed, he were left to rot there which human justice called forgetting. Between mankind and the condemned, weighing upon his head, there was an accumulated mass of stone and jailers; and the whole prison, the massive fortress, was but one enormous complicated lock that barred him from the living world. -It was in one of these deep pits, in the oubliettes excavated by Saint-Louis, in the in pace of the Tournelle doubtless for fear of her escaping that they had deposited Esmeralda, now condemned to the gibbet, with the colossal Palais de Justice over her head poor fly, that could not have moved the smallest of its stones! Truly, Providence and social law alike had been too lavish; such a profusion of misery and torture was not necessary to crush so fragile a creature. -She lay there, swallowed up by the darkness, entombed, walled, lost to the world. Any one seeing her in that state, after beholding her laughing and dancing in the sunshine, would have shuddered. Cold as night, cold as death, no breath of air to stir her locks, no human sound to reach her ear, no ray of light within her eye broken, weighed down by chains, crouching beside a pitcher and a loaf of bread, on a heap of straw, in the pool of water formed by the oozings of the dungeon walls motionless, almost breathless, she was even past suffering. Ph?bus, the sun, noonday, the free air, the streets of Paris, dancing and applause, her tender love passages with the officer then the priest, the old hag, the dagger, blood, torture, the gibbet all this passed in turn before her mind, now as a golden vision of delight, now as a hideous nightmare; but her apprehension of it all was now merely that of a vaguely horrible struggle in the darkness, or of distant music still playing above ground but no longer audible at the depth to which the unhappy girl had fallen. -Since she had been here she neither waked nor slept. In that unspeakable misery, in that dungeon, she could no more distinguish waking from sleeping, dreams from reality, than day from night. All was mingled, broken, floating confusedly through her mind. She no longer felt, no longer knew, no longer thought anything definitely at most she dreamed. Never has human creature been plunged deeper into annihilation. -Thus benumbed, frozen, petrified, scarcely had she remarked at two or three different times the sound of a trap-door opening somewhere above her head, without even admitting a ray of light, and through which a hand had thrown her down a crust of black bread. Yet this was her only surviving communication with mankind the periodical visit of the jailer. -One thing alone still mechanically occupied her ear: over her head the moisture filtered through the mouldy stones of the vault, and at regular intervals a drop of water fell from it. She listened stupidly to the splash made by this dripping water as it fell into the pool beside her. -This drop of water falling into the pool was the only movement still perceptible around her, the only clock by which to measure time, the only sound that reached her of all the turmoil going on on earth; though, to be quite accurate, she was conscious from time to time in that sink of mire and darkness of something cold passing over her foot or her arm, and that made her shiver. -How long had she been there? She knew not. She remembered a sentence of death being pronounced somewhere against some one, and then that she herself had been carried away, and that she had awakened in silence and darkness, frozen to the bone. She had crawled along on her hands and knees, she had felt iron rings cutting her ankles, and chains had clanked. She had discovered that all around her were walls, that underneath her were wet flag-stones and a handful of straw but there was neither lamp nor air-hole. Then she had seated herself upon the straw, and sometimes for a change of position on the lowest step of a stone flight she had come upon in the dungeon. -Once she had tried to count the black minutes marked for her by the drip of the water; but soon this mournful labour of a sick brain had discontinued of itself and left her in stupor once more. -At length, one day or one night (for mid-day and mid-night had the same hue in this sepulchre) she heard above her a louder noise than the turnkey generally made when bringing her loaf of bread and pitcher of water. She raised her head, and was aware of a red gleam of light through the crevices of the sort of door or trap in the roof of the vault. At the same time the massive lock creaked, the trap-door grated on its hinges, fell back, and she saw a lantern, a hand, and the lower part of the bodies of two men, the door being too low for her to see their heads. The light stabbed her eyes so sharply that she closed them. -When she opened them again the door was closed, the lantern placed on one of the steps, and one of the two men alone was standing before her. A black monks robe fell to his feet, a cowl of the same hue concealed his face; nothing of his person was visible, neither his face nor his hands it was simply a tall black shroud under which you felt rather than saw that something moved. For some moments she regarded this kind of spectre fixedly, but neither she nor it spoke. They might have been two statues confronting one another. Two things only seemed alive in this tomb: the wick of the lantern that sputtered in the night air and the drop of water falling with its monotonous splash from the roof and making the reflection of the light tremble in concentric circles on the oily surface of the pool. -At last the prisoner broke the silence. Who are you? -A priest. -The word, the tone, the voice made her start. -The priest continued in low tones: -Are you prepared? -For what? -For death. -Oh! she exclaimed, will it be soon? -To-morrow. -Her head, raised with joy, fell again on her bosom. -Tis very long to wait, she sighed; why not to-day? It could not matter to them. -You are, then, very wretched? asked the priest after another silence. -I am very cold, said she. -She took her two feet in her hands the habitual gesture of the unfortunate who are cold, and which we have already remarked in the recluse of the Tour-Roland and her teeth chattered. -From under his hood the priests eyes appeared to be surveying the dungeon. No light! no fire! in the water! tis horrible! -Yes, she answered with the bewildered air which misery had given her. The day is for every one, why do they give me only night? -Do you know, resumed the priest after another silence, why you are here? -I think I knew it once, she said pressing her wasted fingers to her brow as if to aid her memory; but I do not know now. -Suddenly she began to weep like a child. I want to go away from here, sir. I am cold, I am frightened, and there are beasts that crawl over me. -Well, then follow me! And so saying, the priest seized her by the arm. The unhappy girl was already frozen to the hearts core, but yet that hand felt cold to her. -Oh, she murmured, tis the icy hand of Death! Who are you? -The priest raised his cowl. She looked it was the sinister face that had so long pursued her, the devilish head that she had seen above the adored head of her Ph?bus, the eye that she had last seen glittering beside a dagger. -This apparition, always so fatal to her, which thus had thrust her on from misfortune to misfortune, even to an ignominious death, roused her from her stupor. The sort of veil that seemed to have woven itself over her memory was rent aside. All the details of her grewsome adventures, from the nocturnal scene at La Falourdels to her condemnation at La Tournelle, came back to her with a rush not vague and confused as heretofore, but distinct, clear-cut, palpitating, terrible. These recollections, well-nigh obliterated by excess of suffering, revived at sight of that sombre figure, as the heat of the fire brings out afresh upon the blank paper the invisible writing traced on it by sympathetic ink. She felt as if all the wounds of her heart were reopened and bleeding at once. -Ah! she cried, her hands covering her face with a convulsive shudder, it is the priest! -Then she let her arms drop helplessly and sat where she was, her head bent, her eyes fixed on the ground, speechless, shaking from head to foot. -The priest gazed at her with the eye of the kite which after long hovering high in the air above a poor lark cowering in the corn, gradually and silently lessening the formidable circles of its flight, now suddenly makes a lightning dart upon its prey and holds it panting in its talons. -Finish, she murmured in a whisper, finish the last blow! And her head shrank in terror between her shoulders like the sheep that awaits the death-stroke of the butcher. -You hold me in horror then? he said at last. -She made no reply. -Do you hold me in horror? he repeated. -Her lips contracted as if she smiled. Go to, said she, the executioner taunts the condemned! For months he has pursued me, threatened me, terrified me! But for him, my God, how happy I was! It is he who has cast me into this pit! Oh, heavens! it is he who has killed it is he who has murdered him my Ph?bus! -Here, bursting into tears, she lifted her eyes to the priest. Oh, wretch! who are you? what have I done to you that you should hate me so? Alas! what have you against me? -I love thee! cried the priest. -Her tears ceased suddenly. She regarded him with an idiotic stare. He had sunk on his knees before her and enveloped her in a gaze of flame. -Dost thou hear? I love thee! he cried again. -What love is that! she shuddered. -The love of the damned! he answered. -Both remained silent for some minutes, crushed under the load of their emotion he distraught, she stupefied. -Listen, the priest began at last, and a strange calm had come over him; thou shalt know all. I am going to tell thee what have hitherto scarcely dared to say to myself when I furtively searched my conscience in those deep hours of the night, when it seems so dark that God himself can see us no longer. Listen. Before I saw thee, girl, I was happy. -And I, she faintly murmured. -Do not interrupt me Yes, I was happy, or at least judged myself to be so. I was pure my soul was filled with limpid light. No head was lifted so high, so radiantly as mine. Priests consulted me upon chastity, ecclesiastics upon doctrine. Yes, learning was all in all to me it was a sister, and a sister sufficed me. Not but what, in time, other thoughts came to me. More than once my flesh stirred at the passing of some female form. The power of sex and of a mans blood that, foolish adolescent, I had thought stifled forever, had more than once shaken convulsively the iron chain of the vows that rivet me, hapless wretch, to the cold stones of the altar. But fasting, prayer, study, the mortifications of the cloister again restored the empire of the soul over the body. Also I strenuously avoided women. Besides, I had but to open a book, and all the impure vapours of my brain were dissipated by the splendid beams of learning; the gross things of this earth fled from before me, and I found myself once more calm, serene, and joyous in the presence of the steady radiance of eternal truth. So long as the foul fiend only sent against me indefinite shadows of women passing here and there before my eyes, in the church, in the streets, in the fields, and which scarce returned to me in my dreams, I vanquished him easily. Alas! if it stayed not with me, the fault lies with God, who made not man and the demon of equal strength. Listen. One day -Here the priest stopped, and the prisoner heard sighs issuing from his breast which seemed to tear and rend him. -He resumed. One day I was leaning at the window of my cell. What book was I reading? Oh, all is confusion in my mind I was reading. The window overlooked an open square. I heard a sound of a tambourine and of music. Vexed at being thus disturbed in my meditation, I looked into the square. What I saw, there were others who saw it too, and yet it was no spectacle meet for mortal eyes. There, in the middle of the open space it was noon a burning sun a girl was dancing but a creature so beautiful that God would have preferred her before the Virgin would have chosen her to be His mother if she had existed when He became man. Her eyes were dark and radiant; amid her raven tresses where the sun shone through were strands that glistened like threads of gold. Her feet were invisible in the rapidity of their movement, as are the spokes of a wheel when it turns at high speed. Round her head, among her ebon tresses, were discs of metal that glittered in the sun and formed about her brows a diadem of stars. Her kirtle, thick-set with spangles, twinkled all blue and studded with sparks like a summers night. Her brown and supple arms twined and untwined themselves about her waist like two scarfs. Her form was of bewildering beauty. Oh, the dazzling figure that stood out luminous against the very sunlight itself! Alas, girl, it was thou! Astounded, intoxicated, enchanted, I suffered myself to gaze upon thee. I watched thee long till suddenly I trembled with horror I felt that Fate was laying hold on me. -Gasping for breath, the priest ceased speaking for a moment, then he went on: -Already half-fascinated, I strove to cling to something, to keep myself from slipping farther. I recalled the snares which Satan had already laid for me. The creature before me had such supernatural beauty as could only be of heaven or hell. That was no mere human girl fashioned out of particles of common clay and feebly illumined from within by the flickering ray of a womans soul. It was an angle! but of darkness of flame, not of light. At the same moment of thinking thus, I saw near thee a goat a beast of the witches Sabbath, that looked at me and grinned. The midday sun gilded its horns with fire. Twas then I caught sight of the devils snare, and I no longer doubted that thou camest from hell, and that thou wast sent from thence for my perdition. I believed it. -The priest looked the prisoner in the face and added coldly: -And I believe so still. However, the charm acted by degrees; thy dancing set my brain in a maze; I felt the mysterious spell working within me. All that should have kept awake fell asleep in my soul, and like those who perish in the snow, I found pleasure in yielding to that slumber. All at once thou didst begin to sing. What could I do, unhappy wretch that I was. Thy song was more enchanting still than thy dance. I tried to flee. Impossible. I was nailed, I was rooted to the spot. I felt as if the stone floor had risen and engulfed me to the knees. I was forced to remain to the end. My feet were ice, my head was on fire. At length thou didst, mayhap, take pity on me thou didst cease to sing didst disappear. The reflection of the dazzling vision, the echo of the enchanting music, died away by degrees from my eyes and ears. Then I fell into the embrasure of the window, more stark and helpless than a statue loosened from the pedestal. The vesper bell awoke me. I rose I fled; but alas! there was something within me fallen to arise no more something had come upon me from which I could not flee. -Again he paused and then resumed: Yes, from that day onward there was within me a man I did not know. I had recourse to all my remedies the cloister, the altar, labour, books. Useless folly! Oh, how hollow does science sound when a head full of passion strikes against it in despair! Knowest thou, girl, what it was that now came between me and my books? It was thou, thy shadow, the image of the radiant apparition which had one day crossed my path. But that image no longer wore the same bright hue it was sombre, funereal, black as the dark circle which haunts the vision of the imprudent eye that has gazed too fixedly at the sun. -Unable to rid myself of it; with thy song forever throbbing in my ear, thy feet dancing on my breviary, forever in the night-watches and in my dreams feeling the pressure of thy form against my side I desired to see thee closer, to touch thee, to know who thou wert, to see if I should find thee equal to the ideal image that I had retained of thee. In any case, I hoped that a new impression would efface the former one, for it had become insupportable. I sought thee out, I saw thee again. Woe is me! When I had seen thee twice, I longed to see thee a thousand times, to gaze at thee forever. After that how stop short on that hellish incline? after that my soul was no longer my own. The other end of the thread which the demon had woven about my wings was fastened to his cloven foot. I became vagrant and wandering like thyself I waited for thee under porches I spied thee out at the corners of streets I watched thee from the top of my tower. Each evening I returned more charmed, more despairing, more bewitched, more lost than before. -I had learned who thou wast a gipsy a Bohemian a gitana a zingara. How could I doubt of the witchcraft? Listen. I hoped that a prosecution would rid me of the spell. A sorceress had bewitched Bruno of Ast; he had her burned, and was cured. I knew this. I would try this remedy. First, I had thee forbidden the Parvis of Notre-Dame, hoping to forget thee if thou camest no more. Thou didst not heed it. Thou camest again. Then I had the idea of carrying thee off. One night I attempted it. We were two of us. Already we had thee fast, when that miserable officer came upon the scene. He delivered thee, and so began thy misfortunes and mine and his own as well. At length, not knowing what to do or what was to become of me, I denounced thee to the Holy Office. -I thought that I should thus be cured like Bruno of Ast. I thought too, confusedly, that a prosecution would deliver thee into my hands, that once in prison I should hold thee, that thou couldst not then escape me that thou hadst possessed me long enough for me to possess thee in my turn. When one sets out upon an evil path, one should go the whole way tis madness to stop midway in the monstrous! The extremity of crime has its delirium of joy. A priest and a witch may taste of all delights in one anothers arms on the straw pallet of a dungeon. -So I denounced thee. Twas then I began to terrify thee whenever I met thee. The plot which I was weaving against thee, the storm which I was brewing over thy head, burst from me in muttered threats and lightning glances. And yet I hesitated. My project had appalling aspects from which I shrank. -It may be that I would have renounced it that my hideous thought would have withered in my brain without bearing fruit. I thought it would always depend on myself either to follow up or set aside this prosecution. But every evil thought is inexorable and will become an act; and there, where I thought myself all-powerful, Fate was more powerful than I. Alas! alas! tis Fate has laid hold on thee and cast thee in among the dread wheels of the machinery I had constructed in secret! Listen. I have almost done. -One day it was again a day of sunshine a man passes me who speaks thy name and laughs with the gleam of lust in his eyes. Damnation! I followed him. Thou knowest the rest -He ceased. -The girl could find but one word Oh, my Ph?bus! -Not that name! exclaimed the priest, grasping her arm with violence. Utter not that name! Oh, wretched that we are, tis that name has undone us! Nay, rather we have all undone one another through the inexplicable play of Fate! Thou art suffering, art thou not? Thou art cold; the darkness blinds thee, the dungeon wraps thee round; but mayhap thou hast still more light shining within thee were it only thy childish love for the fatuous being who was trifling with thy heart! while I I bear the dungeon within me; within, my heart is winter, ice, despair black night reigns in my soul! Knowest thou all that I have suffered? I was present at the trial. I was seated among the members of the Office. Yes, one of those priestly cowls hid the contortions of the damned. When they led thee in, I was there; while they questioned thee, I was there. Oh, den of wolves! It was my own crime my own gibbet that I saw slowly rising above thy head. At each deposition, each proof, each pleading, I was present I could count thy every step along that dolorous path. I was there, too, when that wild beast oh, I had not foreseen the torture! Listen. I followed thee into the chamber of anguish; I saw thee disrobed and half-naked under the vile hands of the torturer; saw thy foot that foot I would have given an empire to press one kiss upon and die; that foot which I would have rejoiced to feel crushing my head that foot I saw put into the horrible boot that turns the limbs of a human being into a gory pulp. Oh, miserable that I am! While I looked on at this, I had a poniard under my gown with which I lacerated my breast. At thy cry I plunged it into my flesh a second cry from thee and it should have pierced my heart. Look I believe it still bleeds. -He opened his cassock. His breast was indeed scored as by tigers claws, and in his side was a large, badly healed wound. -The prisoner recoiled in horror. -Oh, girl! cried the priest, have pity on me! Thou deemest thyself miserable alas! alas! thou knowest not what misery is. Oh, to love a woman to be a priest to be hated to love her with all the fury of ones soul, to feel that for the least of her smiles one would give ones blood, ones vitals, fame, salvation, immortality, and eternity this life and the life to come; to regret not being a king, a genius, an emperor, an archangel God that one might place a greater slave beneath her feet; to clasp her day and night in ones dreams, ones thoughts and then to see her in love with the trappings of a soldier, and have naught to offer her but the unsightly cassock of a priest, which she will only regard with fear and disgust! To be present with ones jealousy and rage while she lavishes on a miserable, brainless swashbuckler her whole treasure of love and beauty! To see the form that enflames you, that soft bosom, that flesh panting and glowing under the kisses of another! Dear heaven to adore her foot, her arm, her shoulder, to dream of her blue veins, her sun-browned skin till one writhes whole nights upon the stones of ones cell, and to see all those caresses, which one has dreamed of lavishing on her, end in her torture! To have succeeded only in laying her on the bed of leather! Oh, these are the irons heated in the fires of hell! Oh, blest is he who is sawn asunder, torn by four horses! Knowest thou what that torture, is, endured through long nights from seething arteries, a breaking heart, a bursting head burying your teeth in your own hands fell tormentors that unceasingly turn you as on a burning gridiron over a thought of love, of jealousy, and of despair! Have mercy, girl! One moments respite from my torment a handful of ashes on this white heat! Wipe away, I conjure thee, the drops of agony that trickle from my brow! Child, torture me with one hand, but caress me with the other! Have pity, girl have pity on me! -The priest writhed on the wet floor and beat his head against the corner of the stone steps. The girl listened to him gazed at him. -When he ceased, exhausted and panting, she repeated under her breath: Oh, my Ph?bus! -The priest dragged himself to her on his knees. -I beseech thee, he cried, if thou hast any bowels of compassion, repulse me not! Oh, I love thee! I am a wretch! When thou utterest that name, unhappy girl, tis as if thou wert grinding every fibre of my heart between thy teeth! Have pity! if thou comest from hell, I go thither with thee. I have done amply to deserve that. The hell where thou art shall be my paradise the sight of thee is more to be desired than that of God! Oh, tell me, wilt thou have none of me? I would have thought the very mountains had moved ere a woman would have rejected such a love! Oh, if thou wouldst how happy we could be! We would flee I could contrive thy escape we would go somewhere we would seek that spot on earth where the sun shines brightest, the trees are most luxuriant, the sky the bluest. We would love would mingle our two souls togetherwould each have an inextinguishable thirst for the other, which we would quench at the inexhaustible fountain of our love! -She interrupted him with a horrible and strident laugh: -Look, holy father, there is blood upon your nails! -The priest remained for some moments as if petrified, his eyes fixed on his hand. -Well, be it so, he continued at last, with strange claim; insult me, taunt me, overwhelm me with scorn, but come come away. Let us hasten. Tis for to-morrow I tell thee. The gibbet of La Grve thou knowest it is always in readiness. Tis horrible! to see thee carried in that tumbrel! Oh, have pity! I never felt till now how much I loved thee. Oh, follow me! Thou shalt take time to love me after I have saved thee. Thou shalt hate me as long as thou wilt but come To-morrow to-morrow the gibbet! thy execution! Oh, save thyself! spare me! -He seized her by the arm distractedly and sought to drag her away. -She turned her fixed gaze upon him. What has become of Ph?bus? -Ah, said the priest, letting go her arm, you have no mercy! -What has become of Ph?bus? she repeated stonily. -Dead! cried the priest. -Dead? said she, still icy and motionless; then why talk to me of living? -He was not listening to her. -Ah, yes, he said, as if speaking to himself, he must be dead. The knife went deep. I think I reached his heart with the point. Oh, my soul was in that dagger to the very point! -The girl threw herself upon him with the fury of a tigress, and thrust him towards the steps with supernatural strength. -Begone, monster! Begone, assassin! Leave me to die! May the blood of both of us be an everlasting stain upon thy brow! Be thine, priest? Never! never! no power shall unite us not hell itself! Begone, accursed never! -The priest stumbled against the steps. He silently disengaged his feet from the folds of his robe, took up his lantern, and began slowly to ascend the steps leading to the door. He opened the door and went out. -Suddenly she saw his head reappear. His face wore a frightful expression, and he cried with a voice hoarse with rage and despair: -I tell thee he is dead! -She fell on her face to the floor. No sound was now audible in the dungeon but the tinkle of the drop of water which ruffled the surface of the pool in the darkness. -Chapter 5 - The Mother -I doubt if there be anything in the world more enchanting to a mothers heart than the thoughts awakened by the sight of her childs little shoe more especially when it is the holiday shoe, the Sunday, the christening shoe the shoe embroidered to the very sole, a shoe in which the child has not yet taken a step. The shoe is so tiny, has such a charm in it, it is so impossible for it to walk, that it is to the mother as if she saw her child. She smiles as it, kisses it, babbles to it; she asks herself if it can be that there is a foot so small, and should the child be absent, the little shoe suffices to bring back to her vision the sweet and fragile creature. She imagines she sees it she does see it living, laughing, with its tender hands, its little round head, its dewy lips, its clear bright eyes. If it be winter, there it is creeping about the carpet, laboriously clambering over a stool, and the mother trembles lest it come too near the fire. If it be summer, it creeps about the garden, plucks up the grass between the stones, gazes with the artless courage of childhood at the great dogs, the great horses, plays with the shell borders, with the flowers, and makes the gardener scold when he finds sand in the flower-beds and earth on all the paths. The whole world smiles, and shines, and plays round it like itself, even to the breeze and the sunbeams that wanton in its curls. The shoe brings up all this before the mothers eye, and her heart melts thereat like wax before the fire. -But if the child be lost, these thousand images of joy, of delight, of tenderness crowded round the little shoe become so many pictures of horror. The pretty embroidered thing is then an instrument of torture eternally racking the mothers heart. It is still the same string that vibrates the deepest, most sensitive of the human heart but instead of the caressing touch of an angels hand, it is a demons horrid clutch upon it. -One morning, as the May sun rose into one of those deep blue skies against which Garofalo loves to set his Descents from the Cross, the recluse of the Tour-Roland heard a sound of wheels and horses and the clanking of iron in the Place de Grve. But little moved by it, she knotted her hair over her ears to deaden the sound, and resumed her contemplation of the object she had been adoring on her knees for fifteen years. That little shoe, as we have already said, was to her the universe. Her thoughts were wrapped up in it, never to leave it till death. What bitter imprecations she had sent up to heaven, what heart-rending plaints, what prayers and sobs over this charming rosy toy, the gloomy cell of the Tour-Roland alone knew. Never was greater despair lavished upon a thing so engaging and so pretty. -On this morning it seemed as though her grief found more than usually violent expression, and her lamentations could be heard in the street as she cried aloud in monotonous tones that wrung the heart: -Oh, my child! she moaned, my child! my dear and hapless babe! shall I never see thee more? All hope is over! It seems to me always as if it had happened but yesterday. My God! my God! to have taken her from me so soon, it had been better never to have given her to me at all. Knowest thou not that our children are flesh of our flesh, and that a mother who has lost her child believes no longer in God? Ah, wretched that I am, to have gone out that day! Lord! Lord! to have taken her from me so! Thou canst never have looked upon us together when I warmed her, all sweet and rosy, at my fire when I suckled her when I made her little feet creep up my bosom to my lips! Ah, hadst thou seen that, Lord, thou wouldst have had pity on my joy hadst not taken from me the only thing left for me to love! Was I so degraded a creature, Lord, that thou couldst not look at me before condemning me? Woe! woe is me! there is the shoe but the foot where is it? where is the rest where is the child? My babe, my babe! what have they done with thee? Lord, give her back to me! For fifteen years have I worn away my knees in prayer to thee, O God is that not enough? Give her back to me for one day, one hour, one minute only one minute, Lord, and then cast me into hell for all eternity! Ah, did I but know where to find one corner of the hem of thy garment, I would cling to it with both hands and importune thee till thou wast forced to give me back my child! See its pretty little shoe hast thou no pity on it, Lord? Canst thou condemn a poor mother to fifteen years of such torment? Holy Virgin dear mother in heaven! my Infant Jesus they have taken it from me they have stolen it, they have devoured it on the wild moor have drunk its blood have gnawed its bones; Blessed Virgin, have pity on me! My babe I want my babe! What care I that she is in paradise? I will have none of your angels I want my child! I am a lioness, give me my cub. Oh, I will writhe on the ground I will dash my forehead against the stones I will damn myself, and curse thee, Lord, if thou keepest my child from me! Thou seest that my arms are gnawed all over has the good God no pity? Oh, give me but a little black bread and salt, only let me have my child to warm me like the sun! Alas! O Lord my God, I am the vilest of sinners, but my child made me pious I was full of religion out of love for her, and I beheld thee through her smiles as through an opening in heaven. Oh, let me only once, once more only, once more draw this little shoe on to her sweet rosy little foot, and I will die, Holy Mother, blessing thee! Ah, fifteen years she will be a woman grown now! Unhappy child! is it then indeed true that I shall never see her more? not even in heaven, for there I shall never go. Oh, woe is me! to have to say, There is her shoe, and that is all I shall ever have of her! -The unhappy creature threw herself upon the shoe her consolation and her despair for so many years and her very soul was rent with sobs as on the first day. For to a mother who has lost her child, it is always the first day that grief never grows old. The mourning garments may wear out and lose their sombre hue, the heart remains black as on the first day. -At that moment the blithe, fresh voices of children passing the cell struck upon her ear. Whenever children met her eye or ear, the poor mother would cast herself into the darkest corner of her living sepulchre, as if she sought to bury her head in the stone wall that she might not hear them. This time, contrary to her habit, she started up and listened eagerly, for one of them had said: They are going to hang a gipsy woman to-day. -With the sudden bound of the spider which we have seen rush upon the fly at the shaking of his web, she ran to her loophole which looked out, as the reader knows, upon the Place de Grve. In effect, a ladder was placed against the gibbet, and the hangmans assistant was busy adjusting the chains rusted by the rain. A few people stood round. -The laughing group of children was already far off. The sachette looked about for a passer-by of whom she might make inquiries. Close to her cell she caught sight of a priest making believe to study the public breviary, but who was much less taken up with the lattice-guarded volume than with the gibbet, towards which, ever and anon, he cast a savage, scowling glance. She recognised him as the reverend Archdeacon of Josas, a saintly man. -Father, she asked, who is to be hanged there? -The priest looked at her without replying. She repeated her question. -I do not know, he answered. -Some children passing said that it was an Egyptian woman, said the recluse. -I think it is, returned the priest. Paquette La Chantefleurie broke into a hyena laugh. -Listen, said the Archdeacon, it appears that you hate the gipsy women exceedingly? -Hate them! cried the recluse. They are ghouls and stealers of children! They devoured my little girl, my babe, my only child! I have no heart in my body they have eaten it! -She was terrible. The priest regarded her coldly. -There is one that I hate above the rest, she went on, and that I have cursed a young one about the age my child would be if this ones mother had not devoured her. Each time that this young viper passes my cell my blood boils! -Well, my sister, let your heart rejoice, said the priest, stony as a marble statue on a tomb, for tis that one you will see die. -His head fell upon his breast and he went slowly away. -The recluse waved her arms with joy. I foretold it to her that she would swing up there! Priest, I thank thee! cried she, and she began pacing backward and forward in front of her loophole with dishevelled locks and flaming eyes, striking her shoulder against the wall with the savage air of a caged wolf that has long been hungry and feels that the hour of its repast draws near. -Chapter 6 - Three Various Hearts of Men -Ph?bus, however, was not dead. Men of his sort are not so easily killed. When Ma?tre Philippe Lheulier, the Kings advocate extraordinary, had said to poor Esmeralda: He is dying, it was by mistake or jest. When the Archdeacon said to the condemned girl, He is dead! the fact is that he knew nothing about it; but he believed it to be true, he counted upon it, and hoped it earnestly. It would have been too much to expect that he should give the woman he loved good tidings of his rival. Any man would have done the same in his place. -Not indeed that Ph?buss wound had not been serious, but it had been less so than the Archdeacon flattered himself. The leech, to whose house the soldiers of the watch had conveyed him in the first instance, had, for a week, feared for his life, and, indeed, had told him so in Latin. But youth and a vigorous constitution had triumphed, and, as often happens, notwithstanding prognostics and diagnostics, Nature had amused herself by saving the patient in spite of the physician. It was while he was still stretched upon a sick-bed that he underwent the first interrogations at the hands of Philippe Lheulier and the examiners of the Holy Office, which had annoyed him greatly. So, one fine morning, feeling himself recovered, he had left his gold spurs in payment to the man of drugs, and had taken himself off. For the rest, this had in no way impeded the course of justice. The law of that day had but few scruples about the clearness and precision of the proceedings against a criminal. Provided the accused was finally hanged, that was sufficient. As it was, the judges had ample proof against Esmeralda. They held Ph?bus to be dead, and that decided the matter. -As to Ph?bus, he had fled to no great distance. He had simply rejoined his company, then on garrison duty at Queueen-Brie, in the province of ?le de France, a few stages from Paris. -After all, he had no great desire to appear in person at the trial. He had a vague impression that he would cut a somewhat ridiculous figure. Frankly, he did not quite know what to make of the whole affair. Irreligious, yet credulous like every soldier who is nothing but a soldier, when he examined the particulars of that adventure, he was not altogether without his suspicions as to the goat, as to the curious circumstances of his first meeting with Esmeralda, as to the means, no less strange, by which she had betrayed the secret of her love, as to her being a gipsy, finally as to the spectre-monk. He discerned in all these incidents far more of magic than of love probably a witch, most likely the devil; in fine, a drama, or in the language of the day, a mystery and a very disagreeable one in which he had an extremely uncomfortable part: that of the person who receives all the kicks and none of the applause. The captain was greatly put out by this; he felt that kind of shame which La Fontaine so admirably defines: -Ashamed as a fox would be, caught by a hen. -He hoped, however, that the affair would not be noised abroad, and that, he being absent, his name would hardly be mentioned in connection with it; or, at any rate, would not be heard beyond the court-room of the Tournelle. And in this he judged aright there was no Criminal Gazette in those days, and as hardly a week passed without some coiner being boiled alive, some witch hanged, or heretic sent to the stake at one or other of the numberless justices of Paris, people were so accustomed to see the old feudal Themis at every crossway, her arms bar and sleeves rolled up, busy with her pitchforks, her gibbets, and her pillories, that scarcely any notice was taken of her. The beau monde of that age hardly knew the name of the poor wretch passing at the corner of the street; at most, it was the populace that regaled itself on these gross viands. An execution was one of the ordinary incidents of the public way, like the brasier of the pie-man or the butchers slaughter-house. The executioner was but a butcher, only a little more skilled than the other. -Ph?bus, therefore, very soon set his mind at rest on the subject of the enchantress Esmeralda, or Similar, as he called her, of the dagger-thrust he had received from the gipsy or the spectre-monk (it mattered little to him which), and the issue of the trial. But no sooner was his heart vacant on that score, than the image of Fleur-de-Lys returned to it for the heart of Captain Ph?bus, like Nature, abhorred a vacuum. -Moreover, Queue-en-Brie was not a diverting place a village of farriers and herd-girls with rough hands, a straggling row of squalid huts and cabins bordering the high-road for half a league in short, a worlds end. -Fleur-de-Lys was his last flame but one, a pretty girl, a charming dot; and so one fine morning, being quite cured of his wound, and fairly presuming that after the interval of two months the business of the gipsy girl must be over and forgotten, the amorous cavalier pranced up in high feather to the door of the ancestral mansion of the Gondelauriers. He paid no attention to a very numerous crowd collecting in the Place du Parvis before the great door of Notre-Dame. Remembering that it was the month of May, he concluded that it was some procession some Whitsuntide or other festivaltied his steed up to the ring at the porch, and gaily ascended the stair to his fair betrothed. -He found her alone with her mother. -On the heart of Fleur-de-Lys the scene of the gipsy with her goat and its accursed alphabet, combined with her lovers long absences, still weighed heavily. Nevertheless, when she saw her captain enter, she found him so handsome in his brand-new doublet and shining baldrick, and wearing so impassioned an air, that she blushed with pleasure. The noble damsel herself was more charming than ever. Her magnificent golden tresses were braided to perfection, she was robed in that azure blue which so well becomes a blonde a piece of coquetry she had learned from Colombe and her eyes were swimming in that dewy languor which is still more becoming. -Ph?bus, who in the matter of beauty had been reduced to the country wenches of Queue-en-Brie, was ravished by Fleur-de-Lys, which lent our officer so pressing and gallant an air that his peace was made forthwith. The Lady of Gondelaurier herself, still maternally seated in her great chair, had not the heart to scold him. As for Fleur-de-Lys, her reproaches died away in tender cooings. -The young lady was seated near the window still engaged upon her grotto of Neptune. The captain leaned over the back of her seat, while she murmured her fond upbraidings. -What have you been doing with yourself these two long months, unkind one? -I swear, answered Ph?bus, somewhat embarrassed by this question, that you are beautiful enough to make an archbishop dream. -She could not repress a smile. -Go to go to, sir. Leave the question of my beauty and answer me. Fine beauty, to be sure! -Well, dearest cousin, I was in garrison. -And where, if you please? and why did you not come and bid me adieu? -At Queue-en-Brie. -Ph?bus was delighted that the first question had helped him to elude the second. -But that is quite near, monsieur; how is it you never once came to see me? -This was seriously embarrassing. -Because well the service and besides, charming cousin, I have been ill. -Ill? she exclaimed in alarm. -Yes wounded. -Wounded! The poor girl was quite upset. -Oh, do not let that frighten you, said Ph?bus carelessly; it was nothing. A quarrel a mere scratch what does it signify to you? -What does it signify to me? cried Fleur-de-Lys, lifting her beautiful eyes full of tears. Oh, you cannot mean what you say. What was it all about I will know. -Well, then, my fair one, I had some words with Mah Fdy you know the lieutenant of Saint-Germain-en-Laye, and each of us ripped up a few inches of the others skin that is all. -The inventive captain knew very well that an affair of honour always sets off a man to advantage in a womans eye. And sure enough, Fleur-de-Lys looked up into his fine face with mingled sensations of fear, pleasure, and admiration. However, she did not feel entirely reassured. -I only hope you are completely cured, my Ph?bus! she said. I am not acquainted with your Mah Fdy; but he must be an odious wretch. And what was this quarrel about? -Here Ph?bus, whose imagination was not particularly creative, began to be rather at a loss how to beat a convenient retreat out of his encounter. -Oh, how should I know? a mere trifle a horse a hasty word! Fair cousin, said he, by way of changing the conversation, what is all this going on in the Parvis? He went to the window. Look, fair cousin, there is a great crowd in the Place. -I do not know, answered Fleur-de-Lys; it seems a witch is to do penance this morning before the church on her way to the gallows. -So entirely did the captain believe the affair of Esmeralda to be terminated, that he took little heed of these words of Fleur-de-Lys. Nevertheless, he asked a careless question or two. -Who is this witch? -I am sure I do not know. -And what is she said to have done? -Again she shrugged her white shoulders. -I do not know. -Oh, by r Lord! exclaimed the mother, there are so many sorceresses nowadays that they burn them, I dare swear, without knowing their names. As well might you try to know the name of every cloud in heaven. But, after all, we may make ourselves easy; the good God keeps his register above. Here the venerable lady rose and approached the window. Lord, she cried, you are right, Ph?bus, there is indeed a great concourse of the people some of them even, God save us, on the very roofs! Ah, Ph?bus, that brings back to me my young days and the entry of Charles VII, when there were just such crowds I mind not precisely in what year. When I speak of that to you it doubtless sounds like something very old, but to me it is as fresh as to-day. Oh, it was a far finer crowd than this! Some of them climbed up on to the battlements of the Porte Saint-Antoine. The King had the Queen on the crupper behind him; and after their highnesses came all the ladies mounted behind their lords. I remember, too, there was much laughter because by the side of Amanyon de Garlande, who was very short, there came the Sire Mate-felon, a knight of gigantic stature, who had killed the English in heaps. It was very fine. Then followed a procession of all the nobles of France, with their oriflammes fluttering red before one. There were some with pennons and some with banners let me think the Sire de Calan had a pennon, Jean de Chateaumorant a banner, and a richer than any of the others except the Duke of Bourbon. Alas! tis sad to think that all that has been, and that nothing of it now remains! -The two young people were not listening to the worthy dowager. Ph?bus had returned to lean over the back of his lady-loves chair a charming post which revealed to his libertine glance so many exquisite things, and enabled him to divine so many more that, ravished by that satin-shimmering skin, he said to himself, How can one love any but a blonde? -Neither spoke. The girl lifted to him, from time to time, a glance full of tenderness and devotion, and their locks mingled in a ray of the vernal sunshine. -Ph?bus, said Fleur-de-Lys suddenly, in a half-whisper, we are to marry in three months swear to me that you have never loved any woman but myself. -I swear it, fairest angel! returned Ph?bus; and his passionate glance combined with the sincere tone of his voice to convince Fleur-de-Lys of the truth of his assertion. And, who knows, perhaps he believed it himself at the moment. -Meanwhile the good mother, rejoiced to see the two young people in such perfect accord, had left the apartment to attend to some domestic matter. Ph?bus was aware of the fact, and this solitude deux so emboldened the enterprising captain that some strange ideas began to arise in his mind. Fleur-de-Lys loved him he was betrothed to her she was alone with him his old inclination for her had revived not perhaps in all its primitive freshness, but certainly in all its ardour after all, it was no great crime to cut a little of ones own corn in the blade. I know not if these thoughts passed distinctly through his mind; but at any rate, Fleur-de-Lys suddenly took alarm at the expression of his countenance. She looked about her and discovered that her mother was gone. -Heavens! said she, blushing and uneasy, I am very hot. -I think, indeed, replied Ph?bus, that it cannot be far from noon. The sun is oppressive the best remedy is to draw the curtain. -No, no! cried the girl; on the contrary, it is air I need. -And like the doe which scents the hounds, she started up, ran to the window, flung it wide, and took refuge on the balcony. Ph?bus, not overpleased, followed her. -The Place de Parvis of Notre-Dame, upon which, as the reader is aware, the balcony looked down, presented at that moment a sinister and unusual appearance, which forthwith changed the nature of the timid damoiselles alarm. -An immense crowd, extending into all the adjacent streets, filled the whole square. The breast-high wall surrounding the Parvis itself would not have sufficed alone to keep it clear; but it was lined by a close hedge of sergeants of the town-guard and arquebusiers, culverin in hand. Thanks to this grove of pikes and arquebuses the Parvis was empty. The entrance to it was guarded by a body of the bishops halberdiers. The great doors of the church were closed, forming a strong contrast to the innumerable windows round the Place, which, open up to the very gables, showed hundreds of heads piled one above another like the cannon-balls in an artillery ground. The prevailing aspect of this multitude was gray, dirty, repulsive. The spectacle they were awaiting was evidently one that has the distinction of calling forth all that is most bestial and unclean in the populace impossible to imagine anything more repulsive than the sounds which arose from this seething mass of yellow caps and frowzy heads, and there were fewer shouts than shrill bursts of laughter more women than men. -From time to time some strident voice pierced the general hum. -Hi there! Mahiet Baliffre! will they hang her here? -Simpleton, this is the penance in her shift the Almighty is going to cough a little Latin in her face! That is always done here at noon. If tis the gallows you want, you must go to the Grve. -Ill go there afterward. -Tell me, La Boucanbry, is it true that she refused to have a confessor? -So they say, La Bechaigne. -Did you ever see such a heathen? -Sir, tis the custom here. The justiciary of the Palais is bound to deliver up the malefactor, ready sentenced for execution if a layman, to the Provost of Paris; if a cleric, to the official court of the bishopric. -Sir, I thank you. -Oh, mon Dieu! said Fleur-de-Lys, the poor creature! -And this thought tinged with sadness the look she cast over the crowd. The captain, much more interested in her than in this dirty rabble, had laid an amorous hand upon her waist. She turned round with a smile half of pleasure, half of entreaty. -Prithee, Ph?bus, let be! If my mother entered and saw your hand -At this moment the hour of noon boomed slowly from the great clock of Notre-Dame. A murmur of satisfaction burst from the crowd. The last vibration of the twelfth stroke had hardly died away before all the heads were set in one direction, like waves before a sudden gust of wind, and a great shout went up from the square, the windows, the roofs: Here she comes! -Fleur-de-Lys clasped her hands over her eyes that she might not see. -Sweetheart, Ph?bus hastened to say, shall we go in? -No, she returned, and the eyes that she had just closed from fear she opened again from curiosity. -A tumbrel drawn by a strong Normandy draught-horse, and closely surrounded by horsemen in violet livery with white crosses, had just entered the Place from the Rue Saint-Pierre aux B?ufs. The sergeants of the watch opened a way for it through the people by vigorous use of their thonged scourges. Beside the tumbrel rode a few officers of justice and the police, distinguishable by their black garments and their awkwardness in the saddle. Ma?tre Jacques Charmolue figured at their head. -In the fatal cart a girl was seated, her hands tied behind her, but no priest by her side. She was in her shift, and her long black hair (it was the custom then not to cut it till reaching the foot of the gibbet) fell unbound about her neck and over her half-naked shoulders. -Through these waving locks more lustrous than the ravens wing you caught a glimpse of a great rough brown rope, writhing and twisting, chafing the girls delicate shoulder-blades, and coiled about her fragile neck like an earthworm round a flower. Below this rope glittered a small amulet adorned with green glass, which, doubtless, she had been allowed to retain, because nothing is refused to those about to die. The spectators raised above her at the windows could see her bare legs as she sat in the tumbrel, and which she strove to conceal as if from a last remaining instinct of her sex. At her feet lay a little goat, also strictly bound. The criminal was holding her ill-fastened shift together with her teeth. It looked as though, despite her extreme misery, she was still conscious of the indignity of being thus exposed half-naked before all eyes. Alas! it is not for such frightful trials as this that feminine modesty was made. -Holy Saviour! cried Fleur-de-Lys excitedly to the captain. Look, cousin! if it is not your vile gipsy girl with the goat! -She turned round to Ph?bus. His eyes were fixed on the tumbrel. He was very pale. -What gipsy girl with a goat? he faltered. -How, returned Fleur-de-Lys, do you not remember? -Ph?bus did not let her finish. I do not know what you mean. -He made one step to re-enter the room, but Fleur-de-Lys, whose jealousy lately so vehement was now reawakened by the sight of the detested gipsy Fleur-de-Lys stopped him by a glance full of penetration and mistrust. She recollected vaguely having heard something of an officer whose name had been connected with the trial of this sorceress. -What ails you? said she to Ph?bus; one would think that the sight of this woman disconcerted you. -Ph?bus forced a laugh. Me? Not the least in the world! Oh, far from it! -Then stay, she returned imperiously, and let us see it out. -So there was nothing for the unlucky captain but to remain. However, it reassured him somewhat to see that the criminal kept her eyes fixed on the bottom of the tumbrel. It was but too truly Esmeralda. In this last stage of ignominy and misfortune, she was still beautiful her great dark eyes looked larger from the hollowing of her cheeks, her pale profile was pure and unearthly. She resembled her former self as a Virgin of Masaccio resembles one of Raphaels frailer, more pinched, more attenuated. -For the rest, there was nothing in her whole being that did not seem to be shaken to its foundations; and, except for her last poor attempt at modesty, she abandoned herself completely to chance, so thoroughly had her spirit been broken by torture and despair. Her body swayed with every jolt of the tumbrel like something dead or disjointed. Her gaze was blank and distraught. A tear hung in her eye, but it was stationary and as if frozen there. -Meanwhile the dismal cavalcade had traversed the crowd amid yells of joy and the struggles of the curious. Nevertheless, in strict justice be it said, that on seeing her so beautiful and so crushed by affliction, many, even the most hard-hearted, were moved to pity. -The tumbrel now entered the Parvis and stopped in front of the great door. The escort drew up in line on either side. Silence fell upon the crowd, and amid that silence, surcharged with solemnity and anxious anticipation, the two halves of the great door opened apparently of themselves on their creaking hinges and disclosed the shadowy depths of the sombre church in its whole extent, hung with black, dimly lighted by a few tapers glimmering in the far distance on the high altar, and looking like a black and yawning cavern in the midst of the sunlit Place. At the far end, in the gloom of the chancel, a gigantic cross of silver was dimly visible against a black drapery that fell from the roof to the floor. The nave was perfectly empty, but the heads of a few priests could be seen stirring vaguely in the distant choir-stalls, and as the great door opened, there rolled from the church a solemn, far-reaching, monotonous chant, hurling at the devoted head of the criminal fragments of the penitential psalms: -Non timebo millia populi circumdantis me. Exsurge, Domine; salvum me fac, Deus! -Salvum me fac, Deus, quoniam intraverunt aqu? usque ad animam meam. -Infixus sum in limo profundi; et non est substantia. -At the same time an isolated voice, not in the choir, intoned from the step of the high altar this impressive offertory: -Qui verbum meum audit, et credit ei qui misit me, habet vitam ?ternam et in judicium non venit; sed transit a morte in vitam. -This chant intoned by a few old men lost in the gloom of the church, and directed at this beautiful creature full of youth and life, wooed by the balmy air of spring, and bathed in sunshine, was the mass for the dead. -The multitude listened with pious attention. -The hapless, terrified girl seemed to lose all sight and consciousness in this view into the dark bowels of the church. Her white lips moved as if she prayed, and when the hang-mans assistant advanced to help her down from the tumbrel, he heard a low murmur from her Ph?bus! -They untied her hands and made her descend from the cart, accompanied by her goat, which they had also unbound, and which bleated with delight at finding itself free. She was then made to walk barefoot over the rough pavement to the bottom of the flight of steps leading up to the door. The rope she had round her neck trailed after her like a serpent in pursuit. -The chant ceased inside the church. A great cross of gold and a file of wax tapers set themselves in motion in the gloom. The halberds of the bishops guard clanked, and a few moments later a long procession of priests in their chasubles and deacons in their dalmatics advancing, solemnly chanting, towards the penitent, came into her view and that of the crowd. But her eye was arrested by the one who led the procession, immediately behind the cross-bearer. -Oh, she murmured with a shudder, tis he again the priest! -It was the Archdeacon. On his left walked the sub-chanter, on his right the precentor, armed with his wand of office. He advanced with head thrown back, his eyes fixed and wide, chanting with a loud voice: -De ventre inferi clamavi, et exaudisti vocem meam. -Et projecisti me in profundum corde maris et flumen circumdedit me. -As he came into the broad daylight under the high Gothic doorway, enveloped in a wide silver cope barred with a black cross, he was so pale, that more than one among the crowd thought that it was one of the marble bishops off some tomb in the choir come to receive on the threshold of the grave her who was about to die. -No less pale and marble than himself, she was scarcely aware that they had thrust a heavy lighted taper of yellow wax into her hand; she did not listen to the raucous voice of the clerk as he read out the terrible wording of the penance; when she was bidden to answer Amen, she answered Amen. -The first thing that brought back to her any life and strength was seeing the priest sign to his followers to retire, and he advanced alone towards her. Then, indeed, she felt the blood rush boiling to her head, and a last remaining spark of indignation flamed up in that numbed and frozen spirit. -The Archdeacon approached her slowly. Even in her dire extremity, she saw his lustful eye wander in jealousy and desire over her half-nude form. Then he said to her in a loud voice: -Girl, have you asked pardon of God for your sins and offences? He bent over her and whispered (the spectators supposing that he was receiving her last confession): Wilt thou be mine? I can save thee yet! -She regarded him steadfastly: Begone, devil, or I will denounce thee! -A baleful smile curled his lips. They would not believe thee. Thou wouldst but be adding a scandal to a crime. Answer quickly! Wilt thou be mine? -What hast thou done with my Ph?bus? -He is dead, said the priest. -At that moment the miserable Archdeacon raised his eyes mechanically, and there, at the opposite side of the Place, on the balcony of the Gondelauriers house, was the captain himself, standing by the side of Fleur-de-Lys. He staggered, passed his hand over his eyes, looked again, murmured a curse, and every feature became distorted with rage. -Then die thou too! he muttered between his teeth. No one shall have thee! Then lifting his hand over the gipsy girl, he cried in a sepulchral voice: I nunc, anima anceps, et sit tibi Deus misericors! -This was the awful formula with which it was customary to close this lugubrious ceremonial. It was the accepted signal from the priest to the executioner. -The people fell upon their knees. -Kyrie eleison! said the priests standing under the arched doorway. -Kyrie eleison! repeated the multitude in that murmur that runs over a sea of heads like the splashing of stormy waves. -Amen, responded the Archdeacon. And he turned his back upon the doomed girl, his head fell on his breast, he crossed his hands, rejoined his train of priests, and vanished a moment afterward with the cross, the tapers and the copes under the dim arches of the cathedral, and his sonorous voice gradually died away in the choir chanting this cry of human despair: -Omnes gurgites tui et fluctus tui super me transierunt! -The intermittent clank of the butt-ends of the guards pikes growing fainter by degrees in the distance, sounded like the hammer of a clock striking the last hour of the condemned. -All this time the doors of Notre-Dame had remained wide open, affording a view of the interior of the church, empty, desolate, draped in black, voiceless, its lights extinguished. -The condemned girl remained motionless on the spot where they had placed her, awaiting what they would do with her. One of the sergeants had to inform Ma?tre Charmolue that matters had reached this point, as during the foregoing scene he had been wholly occupied in studying the bas-relief of the great doorway, which, according to some, represents Abrahams sacrifice, and according to others, the great alchemistic operation the sun being figured by the angel, the fire by the fagot, and the operator by Abraham. -They had much ado to draw him away from this contemplation; but at last he turned round, and at a sign from him, two men in yellow, the executioners assistants, approached the gipsy to tie her hands again. -At the moment of reascending the fatal cart and moving on towards her final scene, the hapless girl was seized perhaps by some last heart-rending desire for life. She raised her dry and burning eyes to heaven, to the sun, to the silvery clouds intermingling with patches of brilliant blue, then she cast them around her, upon the ground, the people, the houses. Suddenly, while the man in yellow was pinioning her arms, she uttered a piercing cry a cry of joy. On the balcony at the corner of the Place she had descried him her love her lord her life Ph?bus! -The judge had lied, the priest had lied it was he indeed, she could not doubt it he stood there alive and handsome, in his brilliant uniform, a plume on his head, a sword at his side. -Ph?bus! she cried, my Ph?bus! and she tried to stretch out her arms to him, but they were bound. -Then she saw that the captain frowned, that a beautiful girl who was leaning upon his arm looked at him with scornful lips and angry eyes; whereupon Ph?bus said some words which did not reach her ear, and they both hastily disappeared through the casement of the balcony, which immediately closed behind them. -Ph?bus! she cried wildly, can it be that thou believest it? -A monstrous thought had just suggested itself to her she remembered that she had been condemned for murder committed on the person of Ph?bus de Chateaupers. -She had borne all till now, but this last blow was too heavy. She fell senseless to the ground. -Come, said Charmolue impatiently, lift her into the cart, and let us be done with it. -No one had yet remarked in the gallery of royal statues immediately over the arches of the doorway a strange spectator, who, until then, had observed all that passed with such absolute immobility, a neck so intently stretched, a face so distorted, that, but for his habiliments half red, half violet he might have been taken for one of the stone gargoyles through whose mouths the long rain-pipes of the Cathedral have emptied themselves for six hundred years. This spectator had lost no smallest detail of all that had taken place before the entrance to Notre-Dame since the hour of noon. At the very beginning, no one paying the least attention to him, he had firmly attached to one of the small columns of the gallery a stout knotted rope, the other end of which reached to the ground. This done, he had settled himself to quietly look on, only whistling from time to time as a blackbird flew past him. -Now, at the moment when the executioners assistants were preparing to carry out Charmolues phlegmatic order, he threw his leg over the balustrade of the gallery, seized the rope with his hands, his knees and his feet, and proceeded to slide down the face of the Cathedral like a drop of water down a window-pane; ran at the two men with the speed of a cat just dropped from a house-top, knocked the pair down with two terrific blows of his fist, picked up the gipsy in one hand as a child would a doll, and with one bound was inside the church, holding the girl high above his head as he shouted in a voice of thunder: -Sanctuary! -This was all accomplished with such rapidity, that had it been night the whole scene might have passed by the glare of a single flash of lightning. -Sanctuary! Sanctuary! roared the crowd, and the clapping of ten thousand hands made Quasimodos single eye sparkle with joy and pride. -This shock brought the girl to her senses. She opened her eyes, looked at Quasimodo, then closed them suddenly as if in terror at the sight of her deliverer. -Charmolue stood dumfounded, and the executioners and the whole escort with him; for once within the walls of Notre-Dame the criminal was inviolable. The Cathedral was a place of sanctuary; all human justice was powerless beyond the threshold. -Quasimodo had halted within the central doorway. His broad feet seemed to rest as solidly on the floor of the church as the heavy Roman pillars themselves. His great shock head was sunk between his shoulders like that of a lion, which likewise has a mane but no neck. The trembling girl hung in his horny hands like a white drapery; but he held her with anxious care, as if fearful of breaking or brushing the bloom off her as if he felt that she was something delicate and exquisite and precious, and made for other hands than his. At moments he seemed hardly to dare to touch her, even with his breath; then again he would strain her tightly to his bony breast as if she were his only possession, his treasure as the mother of this child would have done. His cyclops eye, bent upon her, enveloped her in a flood of tenderness, of grief, and pity, and then rose flashing with determined courage. Women laughed and cried, the crowd stamped with enthusiasm, for at this moment Quasimodo had a beauty of his own. Verily, this orphan, this foundling, this outcast, was wonderful to look upon: he felt himself august in his strength; he looked that society from which he was banished, and against whose plans he had so forcefully intervened, squarely in the face; he boldly defied that human justice from which he had just snatched its prey, all these tigers now forced to gnash their empty jaws, these myrmidons of the law, these judges, these executioners this whole force of the King which he, the meanest of his subjects, had set at naught by the force of God. -Then, too, how affecting was this protection offered by a creature so misshapen to one so unfortunate a girl condemned to death, saved by Quasimodo! the extremes of physical and social wretchedness meeting and assisting one another. -Meanwhile, after tasting his triumph for a few brief moments, Quasimodo suddenly plunged with his burden into the church. The people, ever delighted at a display of prowess, followed him with their eyes through the dim nave, only regretting that he had so quickly withdrawn himself from their acclamations. Suddenly he reappeared at one end of the gallery of royal statues, which he traversed, running like a madman, lifting his booty high in his arms and shouting Sanctuary! The plaudits of the crowd burst forth anew. Having dashed along the gallery, he vanished again into the interior of the Cathedral, and a moment afterward reappeared on the upper platform, still bearing the Egyptian in his arms, still running madly, still shouting Sanctuary! and the multitude still applauding. At last he made his third appearance on the summit of the tower of the great bell, from whence he seemed to show exultingly to the whole city the woman he had rescued, and his thundering voice that voice which was heard so seldom, and never by him at all, repeated thrice with frenzied vehemence, even into the very clouds: Sanctuary! Sanctuary! Sanctuary! -No?l! No?l! roared the people in return, till the immense volume of acclamation resounded upon the opposite shore of the river to the astonishment of the crowd assembled in the Place de Grve, and among them the recluse, whose hungry eye was still fixed upon the gibbet. -BOOK IX -Chapter 1 - Delirium -Claude Frollo was no longer in Notre-Dame when his adopted son so abruptly cut the fatal noose in which the unhappy Archdeacon had caught the Egyptian and himself at the same time. On entering the sacristy, he had torn off alb, cope, and stole, had tossed them into the hands of the amazed verger, escaped by the private door of the cloister, ordered a wherryman of the Terrain to put him across to the left bank of the Seine, and had plunged into the steep streets of the University, knowing not whither he went, meeting at every step bands of men and women pressing excitedly towards the Pont Saint-Michel in the hope of still arriving in time to see the witch hanged pale, distraught, confused, more blinded and scared than any bird of night set free and flying before a troop of children in broad daylight. He was no longer conscious of where he was going, what were his thoughts, his imaginations. He went blindly on, walking, running, taking the streets at random, without any definite plan, save the one thought of getting away from the Grve, the horrible Grve, which he felt confusedly to be behind him. -In this manner he proceeded the whole length of the Montagne Sainte-Genevive, and at last left the town by the Porte Saint-Victor. He continued his flight so long as he could see, on turning round, the bastioned walls of the University, and the sparse houses of the faubourg; but when at last a ridge of rising ground completely hid hateful Paris from his view when he could imagine himself a hundred leagues away from it, in the country, in a desert he stopped and dared to draw a free breath. -Frightful thoughts now crowded into his mind. He saw clearly into his soul and shuddered. He thought of the unfortunate girl he had ruined and who had ruined him. He let his haggard eye pursue the tortuous paths along which Fate had driven them to their separate destinies up to the point of junction where she had pitilessly shattered them one against the other. He thought of the folly of lifelong vows, of the futility of chastity, science, religion, and virtue, of the impotence of God. He pursued these arguments with wicked gusto, and the deeper he sank in the slough the louder laughed the Satan within him. And discovering, as he burrowed thus into his soul, how large a portion Nature had assigned in it to the passions, he smiled more sardonically than before. He shook up from the hidden depths of his heart all his hatred, all his wickedness; and he discovered with the calm eye of the physician examining a patient that this same hatred and wickedness were but the outcome of perverted love that love, the source of every human virtue, turned to things unspeakable in the heart of a priest, and that a man constituted as he was, by becoming a priest, made of himself a demon and he laughed horribly. But suddenly he grew pale again as he contemplated the worst side of his fatal passion of that corrosive, venomous, malignant, implacable love which had brought the one to the gallows and the other to hell her to death, him to damnation. -And then his laugh came again when he remembered that Ph?bus was living; that, after all, the captain was alive and gay and happy, with a finer uniform than ever, and a new mistress whom he brought to see the old one hanged. And he jeered sardonically at himself to think that of all the human beings whose death he had desired, the Egyptian, the one creature he did not hate, was the only one he had succeeded in destroying. -From the captain, his thoughts wandered to the crowd of that morning, and he was seized with a fresh kind of jealousy. He reflected that the people, the whole population, had beheld the woman he loved divested of all but a single garment almost nude. He wrung his hands in agony at the thought that the woman, a mere glimpse of whose form veiled in shadows and seen by his eye alone would have afforded him the supreme measure of bliss, had been given thus, in broad daylight, at high noon, to the gaze of a whole multitude, clad as for a bridal night. He wept with rage over all these mysteries of love profaned, sullied, stripped, withered forever. He wept with rage to think how many impure eyes that ill-fastened garment had satisfied; that this fair creature, this virgin lily, this cup of purity and all delights to which he would only have set his lips in fear and trembling, had been converted into a public trough, as it were, at which the vilest of the populace of Paris, the thieves, the beggars, the lackeys, had come to drink in common of a pleasure shameless, obscene, depraved. -Again, when he sought to picture to himself the happiness that might have been his had she not been a gipsy and he a priest; had Ph?bus not existed, and had she but loved him; when he told himself that a life of serenity and love would have been possible to him too; that at that very moment there were happy couples to be found here and there on earth, whiling away the hours in sweet communings, in orange groves, by the brook-side, under the setting sun or a starry night; and that had God so willed it, he might have made with her one of those thrice-blessed couples, his heart melted in tenderness and despair. -Oh, it was she! still and forever she! that fixed idea that haunted him incessantly, that tortured him, gnawed his brain, wrung his very vitals! He regretted nothing, he repented of nothing; all that he had done he was ready to do again; better a thousand times see her in the hands of the hangman than the arms of the soldier; but he suffered, he suffered so madly that there were moments when he tore his hair in handfuls from his head to see if it had not turned white. -At one moment it occurred to him that this, perhaps, was the very minute at which the hideous chain he had seen in the morning was tightening its noose of iron round that fragile and slender neck. Great drops of agony burst from every pore at the thought. -At another moment he took a diabolical pleasure in torturing himself by bringing before his minds eye a simultaneous picture of Esmeralda as he had seen her for the first time filled with life and careless joy, gaily attired, dancing, airy, melodious and Esmeralda at her last hour, in her shift, a rope about her neck, slowly ascending with her naked feet the painful steps of the gibbet. He brought this double picture so vividly before him that a terrible cry burst from him. -While this hurricane of despair was upheaving, shattering, tearing, bending, uprooting everything within his soul, he gazed absently at the prospect around him. Some fowls were busily pecking and scratching at his feet; bright-coloured beetles ran to and fro in the sunshine; overhead, groups of dappled cloud sailed in a deep-blue sky; on the horizon the spire of the Abbey of Saint-Victor reared its slate obelisk above the rising ground; and the miller of the Butte-Copeaux whistled as he watched the busily turning sails of his mill. All this industrious, orderly, tranquil activity, recurring around him under a thousand different aspects, hurt him. He turned to flee once more. -He wandered thus about the country till the evening. This fleeing from Nature, from life, from himself, from mankind, from God, went on through the whole day. Now he would throw himself face downward on the ground, digging up the young blades of corn with his nails; or he would stand still in the middle of some deserted village street, his thoughts so insupportable that he would seize his head in both hands as if to tear it from his shoulders and dash it on the stones. -Towards the hour of sunset, he took counsel with himself and found that he was well-nigh mad. The storm that had raged in him since the moment that he lost both the hope and the desire to save the gipsy, had left him without one sane idea, one rational thought. His reason lay prostrate on the verge of utter destruction. But two distinct images remained in his mind: Esmeralda and the gibbet. The rest was darkness. These two images in conjunction formed to his mind a ghastly group, and the more strenuously he fixed upon them such power of attention and thought as remained to him, the more he saw them increase according to a fantastic progression the one in grace, in charm, in beauty, in lustre; the other in horror; till, at last, Esmeralda appeared to him as a star, and the gibbet as a huge fleshless arm. Strange to say, during all this torture he never seriously thought of death. Thus was the wretched man constituted; he clung to life may-be, indeed, he saw hell in the background. -Meanwhile night was coming on apace. The living creature still existing within him began confusedly to think of return. He imagined himself far from Paris, but on looking about him he discovered that he had but been travelling in a circle round the University. The spire of Saint-Sulpice and the three lofty pinnacles of Saint-Germain-des-Prs broke the sky-line on his right. He bent his steps in that direction. When he heard the Qui vive? of the Abbots guard round the battlemented walls of Saint-Germain, he turned aside, took a path lying before him between the abbey mill and the lazaretto, and found himself in a few minutes on the edge of the Pr-aux-Clercs the Students Meadow. This ground was notorious for the brawls and tumults which went on in it day and night; it was a hydra to the poor monks of Saint-Germain Quod monachis Sancti Germani pratensis hydra fuit, clericis nova semper dissidionum capita suscitantibus.1 The Archdeacon feared meeting some one there, he dreaded the sight of a human face; he would not enter the streets till the latest moment possible. He therefore skirted the Praux-Clercs, took the solitary path that lay between it and the Dieu-Neuf, and at length reached the water-side. There Dom Claude found a boatman, who for a few deniers took him up the river as far as the extreme point of the island of the City, and landed him on that deserted tongue of land on which the reader has already seen Gringoire immersed in reverie, and which extended beyond the royal gardens parallel to the island of the cattle-ferry. -The monotonous rocking of the boat and the ripple of the water in some degree soothed the unhappy man. When the boatman had taken his departure, Claude remained on the bank in a kind of stupor, looking straight before him and seeing the surrounding objects only through a distorting mist which converted the whole scene into a kind of phantasmagoria. -The exhaustion of a violent grief will often produce this effect upon the mind. -The sun had set behind the lofty Tour-de-Nesle. It was the hour of twilight. The sky was pallid, the river was white. Between these two pale surfaces, the left bank of the Seine, on which his eyes were fixed, reared its dark mass, and, dwindling to a point in the perspective, pierced the mists of the horizon like a black arrow. It was covered with houses, their dim silhouettes standing out sharply against the pale background of sky and river. Here and there windows began to twinkle like holes in a brasier. The huge black obelisk thus isolated between the two white expanses of sky and river particularly wide at this point made a singular impression on Dom Claude, such as a man would experience lying on his back at the foot of Strassburg Cathedral and gazing up at the immense spire piercing the dim twilight of the sky above his head. Only here it was Claude who stood erect and the spire that lay at his feet; but as the river, by reflecting the sky, deepened infinitely the abyss beneath him, the vast promontory seemed springing as boldly into the void as any cathedral spire. The impression on him was therefore the same, and moreover, in this respect, stronger and more profound, in that not only was it the spire of Strassburg Cathedral, but a spire two leagues high something unexampled, gigantic, immeasurable an edifice such as mortal eye had never yet beheld a Tower of Babel. The chimneys of the houses, the battlemented walls, the carved roofs and gables, the spire of the Augustines, the Tour-de-Nesle, all the projections that broke the line of the colossal obelisk heightened the illusion by their bizarre effect, presenting to the eye all the effect of a florid and fantastic sculpture. -In this condition of hallucination Claude was persuaded that with living eye he beheld the veritable steeple of hell. The myriad lights scattered over the entire height of the fearsome tower were to him so many openings into the infernal fires the voices and sounds which rose from it the shrieks and groans of the damned. Fear fell upon him, he clapped his hands to his ears that he might hear no more, turned his back that he might not see, and with long strides fled away from the frightful vision. -But the vision was within him. -When he came into the streets again, the people passing to and fro in the light of the shop-fronts appeared to him like a moving company of spectres round about him. There were strange roarings in his ears wild imaginings disturbed his brain. He saw not the houses, nor road, nor vehicles, neither men nor women, but a chaos of indeterminate objects merging into one another at their point of contact. At the corner of the Rule de la Barillerie he passed a chandlers shop, over the front of which hung, according to immemorial custom, a row of tin hoops garnished with wooden candles, which swayed in the wind and clashed together like castanets. He seemed to hear the skeletons on the gibbets of Montfaucon rattling their bones together. -Oh, he muttered, the night wind drives them one against another, and mingles the clank of their chains with the rattle of their bones! May-be she is there among them! -Confused and bewildered, he knew not where he went. A few steps farther on he found himself on the Pont Saint-Michel. There was a light in a low window close by: he approached it. Through the cracked panes he saw into a dirty room which awakened some dim recollection in his mind. By the feeble rays of a squalid lamp he discerned a young man, with a fair and joyous face, who with much boisterous laughter was embracing a tawdry, shamelessly dressed girl. Beside the lamp sat an old woman spinning and singing in a quavering voice. In the pauses of the young mans laughter the priest caught fragments of the old womans song. It was weird and horrible: -Growl, Grve! bark, Grve! -Spin, spin, my distaff brave! -Let the hangman have his cord -That whistles in the prison yard, -Growl, Grve! bark, Grve! -Hemp that makes the pretty rope, -Sow it widely, give it scope; -Better hemp than wheaten sheaves; -Thief theres none that ever thieves -The pretty rope, the hempen rope. -Growl, Grve! bark, Grve! -To see the girl of pleasure brave -Dangling on the gibbet high, -Every window is an eye. -Growl, Grve! bark, Grve! -And the young man laughed and fondled the girl all the while. The old woman was La Falourdel, the girl was a courtesan of the town, and the young man was his brother Jehan. -He continued to look on at the scene as well see this as any other. -He saw Jehan go to a window at the back of the room, open it, glance across at the quay where a thousand lighted windows twinkled, and then heard him say as he closed the window: -As I live, it is night already! The townsfolk are lighting their candles, and God Almighty his stars. -Jehan returned to his light o love, and smashing a bottle that stood on a table, he exclaimed: Empty, cor-b?uf! and Ive no money! Isabeau, my chuck, I shall never be satisfied with Jupiter till he has turned your two white breasts into two black bottles, that I may suck Beaune wine from them day and night! -With this delicate pleasantry, which made the courtesan laugh, Jehan left the house. -Dom Claude had barely time to throw himself on the ground to escape meeting his brother face to face and being recognised. Happily the street was dark and the scholar drunk. Nevertheless he did notice the figure lying prone in the mud. -Oh! oh! said he, heres somebody has had a merry time of it to-day! -He gave Dom Claude a push with his foot, while the older man held his breath with fear. -Dead drunk! exclaimed Jehan. Bravo, he is full. A veritable leech dropped off a wine cask and bald into the bargain, he added as he stooped. Tis an old man! Fortunate senex! -For all that, Dom Claude heard him say as he continued his way, wisdom is a grand thing, and my brother the Archdeacon is a lucky man to be wise and always have money! -The Archdeacon then rose and hastened at the top of his speed towards Notre-Dame, the huge towers of which he could see rising through the gloom above the houses. -But when he reached the Parvis, breathless and panting, he dared not lift his eyes to the baleful edifice. -Oh, he murmured, can it really be that such a thing took place here to-day this very morning? -He presently ventured a glance at the church. Its front was dark. The sky behind glittered with stars; the crescent moon, in her flight upward from the horizon, that moment touched the summit of the right-hand tower, and seemed to perch, like a luminous bird, on the black edge of the sculptured balustrade. -The cloister gate was shut, but the Archdeacon always carried the key of the tower in which his laboratory was, and he now made use of it to enter the church. -He found it dark and silent as a cavern. By the thick shadows that fell from all sides in broad patches, he knew that the hangings of the mornings ceremony had not yet been removed. The great silver cross glittered far off through the gloom, sprinkled here and there with shining points, like the Milky Way of that sepulchral night. The windows of the choir showed, above the black drapery, the upper extremity of their pointed arches, the stained glass of which, shot through by a ray of moonlight, had only the uncertain colours of the night an indefinable violet, white, and blue, of a tint to be found only in the faces of the dead. To the Archdeacon this half circle of pallid Gothic window-tops surrounding the choir seemed like the mitres of bishops gone to perdition. He closed his eyes, and when he opened them again he thought they were a circle of ghastly faces looking down upon him. -He fled on through the church. Then it seemed to him that the church took to itself life and motion swayed and heaved; that each massive column had turned to an enormous limb beating the ground with its broad stone paw; and that the gigantic Cathedral was nothing but a prodigious elephant, snorting and stamping, with its pillars for legs, its two towers for tusks, and the immense black drapery for caparison. -Thus his de?irium or his madness had reached such a pitch of intensity, that the whole external world had become to the unhappy wretch one great Apocalypse visible, palpable, appalling. -He found one minutes respite. Plunging into the side aisle, he caught sight, behind a group of pillars, of a dim red light. He ran to it as to a star of safety. It was the modest lamp which illumined day and night the public breviary of Notre-Dame under its iron trellis. He cast his eye eagerly over the sacred book, in the hope of finding there some word of consolation or encouragement. The volume lay open at this passage of Job, over which he ran his blood-shot eye: Then a spirit passed before my face, and I felt a little breath, and the hair of my flesh stood up. -On reading these dismal words, he felt like a blind man who finds himself wounded by the stick he had picked up for his guidance. His knees bent under him, and he sank upon the pavement thinking of her who had died that day. So many hideous fumes passed through and out of his brain that he felt as if his head had become one of the chimneys of hell. -He must have remained long in that position past thought, crushed and passive in the clutch of the Fiend. At last some remnant of strength returned to him, and he be-thought him of taking refuge in the tower, beside his faithful Quasimodo. He rose to his feet, and fear being still upon him, he took the lamp of the breviary to light him. It was sacrilege but he was beyond regarding such trifles. -Slowly he mounted the stairway of the tower, filled with a secret dread which was likely to be shared by the few persons traversing the Parvis at that hour and saw the mysterious light ascending so late from loophole to loophole up to the top of the steeple. -Suddenly he felt a breath of cold air on his face, and found himself under the doorway of the upper gallery. The air was sharp, the sky streaked with clouds in broad white streamers, which drifted into and crushed one another like river ice breaking up after a thaw. The crescent moon floating in their midst looked like some celestial bark set fast among these icebergs of the air. -He glanced downward through the row of slender columns which joins the two towers and let his eye rest for a moment on the silent multitude of the roofs of Paris, shrouded in a veil of mist and smoke jagged, innumerable, crowded, and small, like the waves of a tranquil sea in a summers night. -The young moon shed but a feeble ray, which imparted an ashy hue to earth and sky. -At this moment the tower clock lifted its harsh and grating voice. It struck twelve. The priest recalled the hour of noon twelve hours had passed. -Oh, he whispered to himself, she must be cold by now! A sudden puff of wind extinguished his lamp, and almost at the same instant, at the opposite corner of the tower, he saw a shade a something white a shape, a female form appear. He trembled. Beside this woman stood a little goat that mingled its bleating with the last quaverings of the clock. -He had the strength to look. It was she. -She was pale and heavy-eyed. Her hair fell round her shoulders as in the morning, but there was no rope about her neck, her hands were unbound. She was free, she was dead. -She was clad in white raiment, and a white veil was over her head. -She moved towards him slowly looking up to heaven, followed by the unearthly goat. He felt turned to stone too petrified to fly. At each step that she advanced, he fell back that was all. In this manner he re-entered the dark vault of the stairs. He froze at the thought that she might do the same; had she done so, he would have died of horror. -She came indeed as far as the door, halted there for some moments, gazing fixedly into the darkness, but apparently without perceiving the priest, and passed on. She appeared to him taller than he remembered her in life he saw the moon through her white robe he heard her breathe. -When she had passed by, he began to descend the stairs with the same slow step he had observed in the spectre thinking himself a spectre too haggard, his hair erect, the extinguished lamp still in his hand. And as he descended the spiral stairs he distinctly heard a voice laughing and repeating in his ears: Then a spirit passed before my face, and I felt a little breath, and the hair of my flesh stood up. -______________________ -1 Because to the monks of Saint-Germain this meadow was a hydra ever raising its head anew in the brawls of the clerks. -Chapter 2 - Humpbacked, One-eyed, Lame -Down to the time of Louis XII, every town in France had its place of sanctuary, forming, in the deluge of penal laws and barbarous jurisdictions that inundated the cities, islands, as it were, which rose above the level of human justice. Any criminal landing upon one of them was safe. In every town there were almost as many of these places of refuge as there were of execution. It was the abuse of impunity side by side with the abuse of capital punishment two evils seeking to correct one another. The royal palaces, the mansions of the princes, and, above all, the churches, had right of sanctuary. Sometimes a whole town that happened to require repeopling was turned temporarily into a place of refuge. Louis XI made all Paris a sanctuary in 1467. -Once set foot within the refuge, and the person of the criminal was sacred; but he had to beware of leaving it one step outside the sanctuary, and he fell back into the waters. The wheel, the gibbet, the strappado, kept close guard round the place of refuge, watching incessantly for their prey, like sharks about a vessel. Thus, men under sentence of death had been known to grow gray in a cloister, on the stairs of a palace, in the grounds of an abbey, under the porch of a church in so far, the sanctuary itself was but a prison under another name. -It sometimes happened that a solemn decree of parliament would violate the sanctuary, and reconsign the condemned into the hands of the executioner; but this was of rare occurrence. The parliaments stood in great awe of the bishops, and if it did come to a brush between the two robes, the gown generally had the worst of it against the cassock. Occasionally, however, as in the case of the assassination of Petit-Jean, the executioner of Paris, and in that of Emery Rousseau, the murderer of Jean Valleret, justice would overleap the barriers of the Church, and pass on to the execution of its sentence. But, except armed with a decree of parliament, woe betide him who forcibly violated a place of sanctuary! We know what befell Robert de Clermont, Marshal of France, and Jean de Chalons, Marshal of Champagne; and yet it was only about a certain Perrin Marc, a money-changers assistant and a vile assassin; but the two marshals had forced the doors of the Church of Saint-Mry therein lay the enormity of the transgression. -According to tradition, these places of refuge were so surrounded by an atmosphere of reverence that it even affected animals. Thus Aymoin relates that a stag, hunted by King Dagobert, having taken refuge beside the tomb of Saint-Denis, the hounds stopped the chase and stood barking. -The churches usually had a cell set apart for these refugees. In 1407, Nicolas Flamel had one built in Saint-Jacques-de-la-Boucherie which cost him four livres, six sous, sixteen deniers parisis. -In Notre-Dame it was a cell constructed over one of the side aisles, under the buttresses and facing towards the cloister, exactly on the spot where the wife of the present concierge of the towers has made herself a garden which is to the hanging gardens of Babylon as a lettuce to a palm tree, as a portress to Semiramis. -There it was that, after his frantic and triumphant course round the towers and galleries, Quasimodo had deposited Esmeralda. So long as the course had lasted the girl had remained almost unconscious, having only a vague perception that she was rising in the air that she was floating flying being borne upward away from the earth. Ever and anon she heard the wild laugh, the raucous voice of Quasimodo in her ear: she half opened her eyes and saw beneath her confusedly the thousand roofs of Paris, tile and slate like a red and blue mosaic and above her head Quasimodos frightful and jubilant face. Then her eye-lids closed; she believed that all was finished. that she had been executed during her swoon, and that the hideous genio who had ruled her destiny had resumed possession of her soul and was bearing it away. She dared not look at him, but resigned herself utterly. -But when the bell-ringer, panting and dishevelled, had deposited her in the cell of refuge, when she felt his great hands gently untying the cords that cut her arms, she experienced that shock which startles out of their sleep the passengers of a vessel that strikes on a rock in the middle of a dark night. So were her thoughts awakened, and her senses returned to her one by one. She perceived that she was in Notre-Dame, she remembered that she had been snatched from the hands of the executioner, that Ph?bus was living, and that Ph?bus loved her no more; and these last two thoughts the one so sweet, the other so bitter presenting themselves simultaneously to the poor creature, she turned to Quasimodo, who still stood before her, filling her with terror, and said: -Why did you save me? -He looked at her anxiously, striving to divine her words. She repeated her question, at which he gave her another look of profound sadness, and, to her amazement, hastened away. -In a few minutes he returned, carrying a bundle which he threw at her feet. It was some wearing apparel deposited for her by some charitable women. At this she cast down her eyes over her person, saw that she was nearly naked, and blushed. Life was coming back to her. -Quasimodo seemed to feel something of this modest shame. He veiled his eye with his broad hand and left her once more, but this time with reluctant steps. -She hastened to clothe herself in the white robe and the white veil supplied to her. It was the habit of a novice of the H?tel-Dieu. -She had scarcely finished when she saw Quasimodo returning, carrying a basket under one arm and a mattress under the other. The basket contained a bottle and bread and a few other provisions. He set the basket on the ground and said, Eat. He spread the mattress on the stone floor Sleep, he said. -It was his own food, his own bed, that the poor bell-ringer had been to fetch. -The gipsy raised her eyes to him to thank him, but she could not bring herself to utter a word. The poor devil was in truth too frightful. She dropped her head with a shudder. -I frighten you, said he. I am very ugly I know. Do not look upon me. Listen to what I have to say. In the daytime you must remain here, but at night you may go where you will about the church. But go not one step outside the church by day or night. You would be lost. They would kill you, and I should die. -Touched by his words, she raised her head to answer him. He had disappeared. She found herself alone, musing upon the strange words of this almost monster and struck by the tone of his voice so harsh, and yet so gentle. -She presently examined her cell. It was a chamber some six feet square, with a small window and a door following the slight incline of the roofing of flat stones outside. Several gargoyles with animal heads seemed bending down and stretching their necks to look in at her window. Beyond the roof she caught a glimpse of a thousand chimneyCtops from which rose the smoke of the many hearths of Paris a sad sight to the poor gipsy a foundling, under sentence of death, an unhappy outcast without country, or kindred, or home! -At the moment when the thought of her friendless plight assailed her more poignantly than ever before, she was startled everything frightened her now by a shaggy, bearded head rubbing against her knees. It was the poor little goat, the nimble Djali, which had made its escape and followed her at the moment when Quasimodo scattered Charmolues men, and had been lavishing its caresses in vain at her feet for nearly an hour without obtaining a single glance from her. Its mistress covered it with kisses. -Oh, Djali!she exclaimed, how could I have forgotten thee thus? And dost thou still love me? Oh, thou thou art not ungrateful! -And then, as if some invisible hand had lifted the weight which had lain so long upon her heart and kept back her tears, she began to weep, and as the tears flowed all that was harshest and most bitter in her grief and pain was washed away. -When night fell she found the air so sweet, the moonlight so soothing, that she ventured to make the round of the high gallery that surrounds the church; and it brought her some relief, so calm and distant did earth seem to her from that height. -Chapter 3 - Deaf -On waking the next morning, she discovered to her surprise that she had slept poor girl, she had so long been a stranger to sleep. A cheerful ray from the rising sun streamed through her window and fell upon her face. But with the sun something else looked in at her window that frightened her the unfortunate countenance of Quasimodo. Involuntarily she closed her eyes to shut out the sight, but in vain; she still seemed to see through her rosy eyeClids that goblin face oneCeyed, brokenCtoothed, maskClike. Then, while she continued to keep her eyes shut, she heard a grating voice say in gentlest accents: -Be not afraid. I am a friend. I did but come to watch you sleeping. That cannot hurt you, can it, that I should come and look at you asleep? What can it matter to you if I am here so long as your eyes are shut? Now I will go. There, I am behind the wall you may open your eyes again. -There was something more plaintive still than his words, and that was the tone in which they were spoken. Much touched, the gipsy opened her eyes. It was true, he was no longer at the window. She ran to it and saw the poor hunchback crouching against a corner of the wall in an attitude of sorrow and resignation. Overcoming with an effort the repulsion he inspired in her, Come back,she said softly. From the movement of her lips, Quasimodo understood that she was driving him away; he therefore rose and hobbled off slowly, with hanging head, not venturing to lift even his despairing glance to the girl. -Come hither!she called, but he kept on his way. At this she hastened out of the cell, ran after him, and put her hand on his arm. At her touch Quasimodo thrilled from head to foot. He lifted a suppliant eye, and perceiving that she was drawing him towards her, his whole face lit up with tenderness and delight. She would have had him enter her cell, but he remained firmly on the threshold. No, no,said he; the owl goes not into the nest of the lark. -She proceeded, therefore, to nestle down prettily on her couch, with the goat asleep at her feet, and both remained thus for some time motionless, gazing in silence he at so much beauty, she at so much ugliness. Each moment revealed to her some fresh deformity. Her eyes wandered from the bowed knees to the humped back, from the humped back to the cyclops eye. She could not imagine how so misshapen a being could carry on existence. And yet there was diffused over the whole such an air of melancholy and gentleness that she began to be reconciled to it. -He was the first to break the silence. -You were telling me to come back? -She nodded in affirmation and said, Yes. -He understood the motion of her head. Alas!he said, and hesitated as if reluctant to finish the sentence; you see, I am deaf. -Poor soul!exclaimed the gipsy with a look of kindly pity. -He smiled sorrowfully. Ah! you think I was bad enough without that? Yes, I am deaf. That is the way I am made! Tis horrible, in truth. And you you are so beautiful. -In the poor creatures tone there was so profound a consciousness of his pitiable state, that she had not the resolution to utter a word of comfort. Besides, he would not have heard it. He continued: -Never did I realize my deformity as I do now. When I compare myself with you, I do indeed pity myself poor unhappy monster that I am! Confess I look to you like some terrible beast? You you are like a sunbeam, a drop of dew, the song of a bird! While I am something fearsome neither man nor beast a something that is harder, more trodden underfoot, more unsightly than a stone by the wayside!And he laughed the most heartCrending kind of laughter in all the world. -Yes, I am deaf,he went on. But you can speak to me by signs and gestures. I have a master who talks to me in that manner. And then I shall soon know your will by the motion of your lips and by your face. -Well, then,she said, smiling, tell me why you saved me. -He looked at her attentively while she spoke. -I understood,he replied, you were asking why I saved you. You have forgotten a poor wretch who tried to carry you off one night a wretch to whom, next day, you brought relief on the shameful pillory. A drop of water a little pity that is more than my whole life could repay. You have forgotten he remembers. -She listened to him with profound emotion. A tear rose to the bellCringers eye, but it did not fall; he seemed to make it a point of honour that it should not fall. -Listen,he said, when he had regained control over himself. We have very high towers here; a man, if he fell from one, would be dead before he reached the ground. If ever you desire me to throw myself down, you have but to say the word a glance will suffice. -He turned to go. Unhappy as the gipsy girl herself was, this grotesque creature awakened some compassion in her. She signed to him to remain. -No, no,he returned, I may not stay here too long. I am not at my ease while you look at me. It is only from pity that you do not turn away your eyes. I will go to a spot where I can see you without being seen in my turn. It will be better. -He drew from his pocket a little metal whistle. -Here,he said, when you have need of me, when you wish me to come, when you are not too disgusted to look at me, then sound this whistle; I can hear that. -He laid the whistle on the floor and hastened away. -Chapter 4 - Earthenware and Crystal -The days succeeded one another. -Little by little tranquility returned to Esmeraldas spirits. Excess of suffering, like excess of joy, is a condition too violent to last. The human heart is incapable of remaining long in any extreme. The gipsy had endured such agonies that her only remaining emotion at its recollection was amazement. -With the feeling of security hope returned to her. She was outside the pale of society, of life; but she had a vague sense that it was not wholly impossible that she should reCenter it as if dead but having in reserve a key to open her tomb. -The terrible images that had so long haunted her withdrew by degrees. All the grewsome phantoms Pierrat Torterue, Jacques Charmolue, and the rest, even the priest himself faded from her mind. -And then Ph?bus was living; she was sure of it, she had seen him. -The fact of Ph?bus being alive was all in all to her. After the series of earthquake shocks that had overturned everything, left no stone standing on another in her soul, one feeling alone had stood fast, and that was her love for the soldier. For love is like a tree; it grows of itself, strikes its roots deep into our being, and often continues to flourish and keep green over a heart in ruins. -And the inexplicable part of it is, that the blinder this passion the more tenacious is it. It is never more firmly seated than when it has no sort of reason. -Assuredly Esmeralda could not think of the captain without pain. Assuredly it was dreadful that he too should have been deceived, should have believed it possible that the daggerCthrust had been dealt by her who would have given a thousand lives for him. And yet he was not so much to blame, for had she not confessed her crime? Had she not yielded, weak woman that she was, to the torture? The fault was hers, and hers alone. She ought rather to have let them tear the nails from her feet than such an avowal from her lips. Still, could she but see Ph?bus once again, for a single minute, it needed but a word, a look, to undeceive him, to bring him back to her. She did not doubt it for a moment. She closed her eyes to the meaning of various singular things, or put a plausible construction on them: the chance presence of Ph?bus on the day of her penance, the lady who stood beside him his sister, no doubt. The explanation was most unlikely, but she contented herself with it because she wished to believe that Ph?bus still loved her, and her alone. Had he not sworn it to her? And what more did she need simple and credulous creature that she was? Besides, throughout the whole affair, were not appearances far more strongly against her than against him? So she waited she hoped. -Added to this, the church itself, the vast edifice wrapping her round on all sides, protecting, saving her, was a sovereign balm. The solemn lines of its architecture, the religious attitude of all the objects by which the girl was surrounded, the serene and pious thoughts that breathed, so to speak, from every pore of these venerable stones, acted upon her unceasingly. Sounds arose from it, too, of such blessedness and such majesty that they soothed that tortured spirit. The monotonous chants of the priests and the responses of the people sometimes an inarticulate murmur, sometimes a roll of thunder; the harmonious trembling of the windows, the blast of the organ like a hive of enormous bees, that entire orchestra with its gigantic gamut ascending and descending incessantly from the voice of the multitude to that of a single bell deadened her memory, her imagination, her pain. The bells in especial lulled her. A potent magnetism flowed from the vast metal domes and rocked her on its waves. -Thus, each succeeding morn found her calmer, less pale, breathing more freely. And as the wounds of her spirit healed, her outward grace and beauty bloomed forth again, but richer, more composed. Her former character also returned something even of her gaiety, her pretty pout, her love for her goat, her pleasure in singing, her delicate modesty. She was careful to retire into the most secluded corner of her cell when dressing in the mornings, lest some one from the neighbouring attics should see her through the little window. -When her dreams of Ph?bus left her the leisure, the gipsy sometimes let her thoughts stray to Quasimodo the only link, the only means of communication with mankind, with life, that remained to her. Hapless creature! she was more cut off from the world than Quasimodo himself. She knew not what to think of the singular friend whom chance had given her. She often reproached herself that hers was not the gratitude that could veil her eyes, but it was useless she could not accustom herself to the poor bellCringer. He was too repulsive. -She had left the whistle he gave her lying on the ground; which, however, did not prevent Quasimodo from appearing from time to time during the first days. She did her very utmost not to turn away in disgust when he brought her the basket of provisions and the pitcher of water, but he instantly perceived the slightest motion of the kind, and hastened sorrowfully away. -Once he happened to come at the moment she was caressing Djali. He stood a few minutes pensively contemplating the charming group, and at last said, shaking his heavy, misshapen head: -My misfortune is that I am still too much like a man. Would I were a beast outright like that goat! -She raised her eyes to him in astonishment. -He answered her look. Oh, I know very well why.And he went away. -Another time he presented himself at the door of the cell (into which he never entered) while Esmeralda was singing an old Spanish ballad, the words of which she did not understand, but which had lingered in her ear because the gipsy women had sung her to sleep with it when a child. At the sight of the hideous face appearing suddenly, the girl broke off with an involuntary gesture of fright. The unhappy bellCringer fell upon his knees on the threshold, and with a suppliant look clasped his great shapeless hands. Oh!he said in piteous accents, I conjure you to continue do not drive me away!Unwilling to pain him, she tremblingly resumed her song, and by degrees her fright wore off, till she abandoned herself wholly to the slow and plaintive measure of the air. He, the while, had remained upon his knees, his hands clasped as if in prayer attentive, scarcely breathing his gaze fixed on the gipsys radiant eyes. He seemed to hear the music of her voice in those twin stars. -Another time again, he approached her with an awkward and timid air. Listen,said he with an effort, I have something to say to you.She signed to him that she was listening. He sighed deeply, opened his lips, seemed for a moment to be on the point of speaking, then looked her in the face, shook his head, and slowly withdrew, his forehead bowed in his hand, leaving the Egyptian wondering and amazed. -Among the grotesques sculptured on the wall, there was one for which he had a particular affection, and with which he often seemed to exchange fraternal looks. Once the gipsy heard him say to it: Oh! why am I not fashioned of stone like thee? -At length, one morning Esmeralda had advanced to the edge of the roof and was looking down into the Place over the sharp roofCridge of SaintCJean le Rond. Quasimodo stood behind her, as was his habit, that he might spare her as much as possible the pain of seeing him. Suddenly the gipsy started; a tear and a flash of joy shone together in her eyes; she fell on her knees, and stretching out her arms in anguish towards the Place: -Ph?bus!she cried, come! come to me! one word, one single word, for the love of heaven! Ph?bus! Ph?bus! -Her voice, her face, her gesture, her whole attitude had the heartCrending aspect of a shipwrecked mariner making signals of distress to some gay vessel passing on the distant horizon in a gleam of sunshine. -Leaning over in his turn, Quasimodo perceived the object of this tender and agonizing prayer a young man, a soldier, a handsome cavalier glittering in arms and gay attire, who was caracoling through the Place and sweeping his plumed hat to a lady smiling down on him from a balcony. The officer could not hear the unhappy girl calling to him. He was too far off. -But the poor deaf ringer heard. A profound sigh heaved his breast. He turned away. His heart was swelling with the tears he drove back; his two clenched fists went up convulsively to his head, and when he drew them away they each held a handful of his rough red hair. -The Egyptian paid no heed to him. -Damnation!he muttered, as he ground his teeth, so that is how a man should be he need only have a handsome outside! -Meanwhile she was still on her knees crying out in terrible agitation: -Oh! now he is dismounting from his horse he is going into that house Ph?bus! He does not hear me. Ph?bus! The shameless woman, to be speaking to him at the same time that I do! Ph?bus! Ph?bus! -The deaf man watched her. He understood her gestures, and the poor bellCringers eye filled with tears, though he let not one of them fall. Presently he pulled her gently by the hem of her sleeve. She turned round. He had assumed an untroubled mien. -Shall I go and fetch him?he asked quietly. -She gave a cry of joy. Oh, go! Go quickly run! hasten! it is that officer! that officer bring him to me, and I will love thee! -She clasped his knees. He could not refrain from shaking his head mournfully. -I will bring him to you,he said in a low voice; then, turning away his head, he strode to the stairCcase, suffocating with sobs. -By the time he reached the Place there was nothing to be seen but the horse fastened to the door of the Gondelauriers house. The captain had gone in. -Quasimodo looked up at the roof of the Cathedral. Esmeralda was still in the same place, in the same attitude. He made her a melancholy sign of the head, then established himself with his back against one of the posts of the porch, determined to wait until the captain came out. -It was, at the Logis Gondelaurier, one of those gala days which precede a wedding. Quasimodo saw many people go in, but nobody come away. From time to time he looked up at the church roof. The gipsy never stirred from her post any more than he. A groom came, untied the horse and led him away to the stables of the mansion. -The whole day passed thus. Quasimodo leaning against the post, Esmeralda on the roof, Ph?bus, no doubt, at the feet of Fleur-de-Lys. -Night fell at last a dark night without a moon. Quasimodo might strain his gaze towards Esmeralda, she faded into a mere glimmer of light in the gloaming then nothing; all was swallowed up in darkness. -He now saw the whole fa?ade of the Gondelaurier mansion illuminated from top to bottom. He saw one after another the windows in the Place lit up, one after another also he saw the lights disappear from them; for he remained the whole evening at his post. The officer never came out. When the last wayfarer had gone home, when every window of the other houses was dark, Quasimodo, quite alone, remained lost in the shadows. The Parvis of NotreCDame was not lighted in those days. -However, the windows of the Gondelaurier mansion blazed on even after midnight. Quasimodo, motionless, and ever on the alert, saw a ceaseless crowd of moving, dancing shadows pass across the manyCcoloured windows. Had he not been deaf, in proportion as the murmur of slumbering Paris died away, he would have heard more and more distinctly from within the Logis Gondelaurier the sound of revelry, of laughter, and of music. -Towards one in the morning the guests began to depart. Quasimodo, crouching in the deep shadow, watched them all as they passed under the torchClit doorway. The captain was not among them. -He was filled with sadness; now and then he looked up into the air like one weary of waiting. Great black clouds, heavy and ragged, hung in deep festoons under the starry arch of night the cobwebs of the celestial roof. -At one of these moments he suddenly saw the folding glass door on to the balcony, the stone balustrade of which was dimly visible above him, open cautiously and give passage to a couple, behind whom it closed noiselessly. It was a male and female figure, in whom Quasimodo had no difficulty in recognising the handsome captain and the young lady he had seen that morning welcoming the officer from that same balcony. The Place was in complete darkness, and a thick crimson curtain which had fallen over the glass door as soon as it closed, intercepted any ray of light from the apartment within. -The young couple, as far as our deaf spectator could judge without hearing a word of what they said, appeared to abandon themselves to a very tender tteCCtte. The lady had evidently permitted the officer to encircle her waist with his arm, and was not too energetically resisting a kiss. -Quasimodo witnessed this scene from below all the more attractive that it was not intended for any strange eye. With bitterness and pain he looked on at so much happiness, so much beauty. After all, nature was not altogether mute in the poor wretch, and though his back was crooked, his nerves were not less susceptible than another mans. He thought of the miserable share in life that Providence had meted out to him; that woman, and the joys of love, must forever pass him by; that he could never attain to being more than a spectator of the felicity of others. But that which wrung his heart most in this scene, and added indignation to his chagrin, was that the gipsy would suffer were she to behold it. To be sure, the night was very dark, and Esmeralda, if she still remained at her post (and he did not doubt it), was too far off, considering that he himself could barely distinguish the lovers on the balcony; this consoled him somewhat. -Meanwhile the conversation above became more and more ardent. The lady appeared to be entreating the officer to solicit no more from her; but all that Quasimodo could distinguish were the clasped white hands, the mingled smiles and tears, the soft eyes of the girl uplifted to the stars, the mans burning gaze devouring her. -Fortunately for the girl, whose resistance was growing weaker, the door of the balcony opened suddenly, and an elder lady appeared; the fair maid seemed confused, the officer disgusted, and all three returned inside. -A moment afterward a horse clattered under the porch, and the gay officer wrapped in his military cloak passed Quasimodo quickly. -The bellCringer let him turn the corner of the street, and ran after him with his apeClike nimbleness, calling, H there! captain! -The captain drew up. What does this rascal want with me?said he, peering through the darkness at the queer, uncouth figure hobbling after him. -Quasimodo came up to him, and boldly taking the horse by the bridle, said, Follow me, captain; theres one here would have speech of you. -Horns of the devil!growled Ph?bus, heres a villainous, ragged bird methinks Ive seen somewhere before. Now, then, my friend, let go my horses rein, I tell thee -Captain,returned the deaf ringer, are you not asking me who it is? -I am telling thee to let go my horse,retorted Ph?bus impatiently. What does the fellow mean by hanging at my chargers rein? Dost take my beast for a gallows? -Far from leaving hold of the horse, Quasimodo was preparing to turn him round. Unable to explain to himself the officers resistance, he hastened to say: Come, captain, tis a woman awaits you,and he added with an effort, a woman who loves you. -A droll rascal!said the captain, who thinks me obliged to run after every woman that loves me, or says she does; especially, if perchance she is anything like thee, owlCfaced one! Go tell her who sent thee that I am going to be married, and she may go to the devil! -Hark you!cried Quasimodo, thinking with a single word to overcome his hesitation; come, monseigneur, tis the gipsy girl you wot of! -This word did indeed make a tremendous impression on Ph?bus, but not the kind the hunchback expected. It will be remembered that the gallant officer had retired from the balcony with FleurCdeCLys a few minutes before Quasimodo saved the condemned girl out of Charmolues hands. Since then, in all his visits to the Gondelaurier mansion, he had taken good care not to mention the woman, the recollection of whom, after all, was painful to him; and FleurCdeCLys, on her part, had not deemed it politic to tell him that the gipsy was alive. Consequently Ph?bus believed poor Similar,as he called her, to be dead, and whats more, for a month or two. Added to which, the captain had been thinking for some moments past that the night was pitch dark; that, combined with the sepulchral voice and supernatural ugliness of the strange messenger, it was past midnight; that the street was as deserted as on the night the spectreCmonk had accosted him, and that his horse had snorted violently at sight of the hunchback. -The gipsy girl!he exclaimed, almost in fear. How now, comest thou from the other world?and his hand went to his daggerChilt. -Quick, quick!said the hunchback, trying to lead the horse on. This way. -Ph?bus planted a vigorous kick in the middle of his chest. Quasimodos eye flashed. He made as if to throw himself on the captain, but checked himself suddenly. Oh,he exclaimed, tis well for you theres some one that loves you!He laid particular stress on the some one,then dropping the horses bridle, Go your way!he cried. -Ph?bus put spurs to his horse and galloped off, swearing lustily. -Quasimodo watched him disappear down the dark street. Oh,murmured the poor deaf hunchback, to think of refusing that! -He returned to the Cathedral, lit his lamp, and mounted the stairs of the tower. As he had surmised, the gipsy was where he had left her. -The moment she caught sight of him she ran to him. Alone!she cried, clasping her beautiful hands in despair. -I did not find him,answered Quasimodo coldly. -You should have waited the whole night through!she retorted vehemently. -He saw her angry gesture and understood the reproach. I will watch better another time,he said, hanging his head. -Get you gone!said she. -He left her. She was displeased with him. But he had chosen rather to be misjudged by her than give her pain. He kept all the grief to himself. -From that day forward the gipsy saw him no more; he came no more to her cell. At most, she would catch a glimpse now and then of the bellCringers countenance looking mournfully down upon her from the summit of a tower, but directly she perceived him he would vanish. -We must confess that she was not greatly affected by this voluntary withdrawal of the hunchback. In her heart she was grateful to him for it. Nor did Quasimodo delude himself upon the subject. -She saw him no more, but she felt the presence of a good genius about her. Her provisions were renewed by an invisible hand while she slept. One morning she found a cage of birds on her windowCsill. Above her cell there was a sculptured figure that frightened her. She had given evidence of this more than once in Quasimodos presence. One morning (for all these things were done in the night) she woke to find it gone. It had been broken away, and whoever had climbed up to that figure must have risked his life. -Sometimes, in the evening, she would hear a voice, concealed under the leaden eaves of the steeple, singing, as if to lull her to sleep, a melancholy and fantastic song, without rhyme or rhythm, such as a deaf man might make: -Look not on the face, -Maiden, look upon the heart. -The heart of a fair youth is oft unsightly; -There be hearts that cannot hold love long. -Maiden, the pines not fair to see, -Not fair to see as the poplar is, -But it keeps its green the winter through. -Alas, tis vain to speak like this! -What is not fair ought not to be; -Beauty will only beauty love; -April looks not on January. -Beauty is perfect, -Beauty can do all, -Beauty is the only thing that does not live by halves. -The raven flies only by day, -The owl flies only by night, -The swan flies day and night. -One morning when she rose she found two vases full of flowers standing at the window. One of them was of glass, very beautiful in shape and colour, but cracked; it had let all the water in it run out, and the flowers it held were faded. The other was of earthenware, rude and common, but it retained all the water, so that its flowers remained fresh and blooming. -I know not if she acted with intention, but Esmeralda took the faded nosegay and wore it in her bosom all day. -That day the voice from the tower was silent. -She did not greatly care. She passed her days in caressing Djali, in watching the door of the Gondelaurier mansion, in talking to herself about Ph?bus, and crumbling her bread to the swallows. -Besides, she had altogether ceased to see or hear Quasimodo. The poor bellCringer seemed to have disappeared from the church. However, one night as she lay awake thinking of her handsome captain, she was startled by hearing the sound of breathing near her cell. She rose, and saw by the light of the moon a shapeless mass lying across her door. It was Quasimodo sleeping there upon the stones. -Chapter 5 - The Key of the Porte Rouge -Meanwhile public talk had acquainted the Archdeacon with the miraculous manner in which the gipsy girl had been saved. He knew not what his feelings were when he learned this. He had reconciled himself to the thought of Esmeraldas death, and so had regained some peace of mind he had touched the depths of possible affliction. The human heart (and Dom Claude had meditated upon these matters) cannot hold more than a given quantity of despair. When the sponge is soaked, an ocean may pass over it without its absorbing one drop more. -Now Esmeralda dead, the sponge was full; the last word had been said for Dom Claude on this earth. But to know her living, and Ph?bus too, was to take up his martyrdom, his pangs, his schemes and alternatives in short, his whole life again. And Claude was weary of it all. -When he learned the news, he shut himself up in his cell in the cloister. He did not appear at the conferences of the chapter, nor at any of the services of the church, and closed his door to every one, even the bishop. He kept himself thus immured for several weeks. He was judged to be ill, as indeed he was. -What was he doing while shut up thus? With what thoughts was the unhappy man contending. Was he making a last stand against his fatal passion combining some final plan of death for her and perdition for himself? -His Jehan, his beloved brother, his spoiled darling, came once to his door and knocked, swore, entreated, told his name a dozen times over. The door remained closed. -He passed whole days with his face pressed against his window, for from thence he could see the cell of Esmeralda, and often the girl herself with her goat, sometimes with Quasimodo. He remarked the deaf hunchbacks assiduities, his obedience, his delicate and submissive ways with the gipsy. He remembered for he had a long memory, and memory is the scourge of the jealous the peculiar look the bellCringer had fixed upon the dancing girl on a certain evening, and he asked himself what motive could have urged Quasimodo to save her. He was witness of a thousand little scenes between the gipsy and the hunchback, the pantomime of which, seen at that distance and commented on by his passion, seemed very tender to him. He mistrusted the capricious fancy of woman. And presently he was vaguely conscious of entertaining a jealousy such as he never could have anticipated a jealousy that made him redden with shame and indignation. -The captain,thought he, well, that might pass; but this one !The idea overwhelmed him. -His nights were dreadful. Since ever he learned that the gipsy girl was alive, the cold images of spectres and the grave which had possessed him for a whole day, vanished, and the flesh returned to torment him. He writhed upon his bed to know the girl so near him. -Each night his delirious imagination called up Esmeralda before him in all the attitudes most calculated to inflame his blood. He saw her swooning over the stabbed officer, her fair, uncovered bosom crimsoned with the young mans blood at that moment of poignant delight when the Archdeacon had imprinted on her pallid lips that kiss of which, half dead as she was, the unhappy girl had felt the burning pressure. Again he beheld her disrobed by the rude hands of the torturers, saw them lay bare and thrust into the hideous boot with its iron screws her tiny foot, her round and delicate leg, her white and supple knee. He saw that ivory knee alone left visible outside Torterues horrible apparatus. Finally, he pictured to himself the girl in her shift, the rope round her neck, her shoulders and her feet bare, almost naked, as he had seen her that last day, and he clenched his hands in agony, and a long shiver ran through him. -At last one night these images so cruelly inflamed his celibates blood that he tore his pillow with his teeth, leaped from his bed, threw a surplice over his night garment, and left his cell, lamp in hand, haggard, half naked, the fire of madness in his eyes. -He knew where to find the key of the Porte Rouge, the communication between the cloister and the church, and, as we know, he always carried with him a key to the tower stairCcase. -Chapter 6 - Sequel to the key of the Porte Rouge -That night Esmeralda had fallen asleep in her little chamber full of hope and sweet thoughts, the horrors of the past forgotten. She had been sleeping for some time, dreaming, as ever, of Ph?bus, when she seemed to hear some sound. Her slumbers were light and broken the sleep of a bird; the slightest thing awoke her. She opened her eyes. The night was very dark. Nevertheless, she saw a face peering in at her through the window a lamp shed its light on this apparition. The moment it found itself observed by Esmeralda the apparition extinguished the lamp. However, the girl had had time to recognise the features. She closed her eyes in terror. -Oh, she murmured weakly, the priest! -All her past misfortunes flashed like lightning through her mind. She fell back upon her bed frozen with horror. -The next moment she felt something in contact with the whole length of her body which sent such a shudder through her that she started up in bed, wide awake and furious. The priest had glided up beside her and clasped his arms about her. -She tried to scream but could not. -Begone, monster! begone, assassin! she said, in a voice hoarse with passion and dread. -Have pity! have pity! murmured the priest, pressing his lips to her shoulder. -She clutched his tonsured head by its scant remaining locks and strove to repel his kisses as if he had been biting her. -Have pity! repeated the unhappy wretch. Didst thou but know what my love for thee is! Tis fire! tis molten lead a thousand daggers in my heart! -He held her arm fast with a superhuman grip. Let me go! she cried wildly, or I spit in thy face! -He released her. Vilify me strike me be angry do what thou wilt; but in mercy, love me! -She struck him with the fury of a child. She raised her pretty hands to tear his face. Away, demon! -Love me! love me! pleaded the unhappy priest, coming close to her again and answering her blows by caresses. -Suddenly she felt that he was overpowering her. There must be an end to this, said he, grinding his teeth. -She was vanquished, panting, broken, in his arms, at his mercy. She felt a lascivious hand groping over her, and making one supreme effort she screamed, Help! help! a vampire! a vampire! -But no one came. Only Djali was awakened and bleating in terror. -Keep quiet, panted the priest. Suddenly in her struggles the gipsys hand came against something cold and metallic. It was Quasimodos whistle. She seized it with a spasm of relief, put it to her lips, and blew with all her remaining strength. The whistle came clear, shrill, piercing. -What is that? said the priest. Almost as he spoke he felt himself dragged away by vigorous arms; the cell was dark, he could not distinguish clearly who it was that held him, but he heard teeth gnashing with rage, and there was just sufficient light in the gloom to show him the glitter of a great knifeCblade just above his head. -The priest thought he could distinguish the outline of Quasimodo. He supposed it could be no one else. He recollected having stumbled, in entering, over a bundle lying across the outside of the door. Yet, as the newCcomer uttered no word, he knew not what to think. He seized the arm that held the knife. Quasimodo! he cried, forgetting in this moment of danger that Quasimodo was deaf. -In a trice the priest was thrown upon the floor and felt a knee of iron planted on his chest. By the pressure of that knee he recognised the hunchback. But what could he do how make himself known to the other? Night made the deaf man blind. -He was lost. The girl, pitiless as an enraged tigress, would not interfere to save him. The knife was nearing his head it was a critical moment. Suddenly his adversary seemed to hesitate. No blood near her! he said under his breath. -There was no mistaking it was Quasimodos voice. -On this the priest felt the huge hand dragging him out of the cell by the foot; he was to die outside. -Fortunately for him the moon had just risen. As they crossed the threshold a pale ray fell across the priests face. Quasimodo stared at him, a tremor seized him, he relinquished his hold and shrank back. -The gipsy girl, who had stolen to the door, was surprised to see them suddenly change parts; for now it was the priest who threatened and Quasimodo who entreated. -The priest, overwhelming the deaf man with gestures of anger and reproof, motioned vehemently to him to withdraw. -The hunchback hung his head, then went and knelt before the gipsys door. Monseigneur, he said in firm but resigned tones, you will do as you think fit afterward, but you will have to kill me first. So saying, he offered his knife to the priest. -Claude, beside himself with passion, put out his hand to seize it, but the girl was too quick for him. She snatched the knife from Quasimodo and burst into a frantic laugh. Now come! she cried to the priest. -She held the blade aloft. The priest faltered she would most certainly have struck. You dare not approach me, coward! she cried. Then she added in a pitiless tone, and knowing well that she was plunging a thousand redChot irons into the priests heart: Ha! I know that Ph?bus is not dead! -The priest threw Quasimodo to the ground with a furious kick; then, trembling with passion, hurled himself into the darkness of the stairCcase. -When he was gone, Quasimodo picked up the whistle which had just been the means of saving the gipsy. It was getting rusty, was all he said as he handed it back to her; then he left her to herself. -Overpowered by the violent scene, the girl sank exhausted upon her couch and broke into bitter sobs. Her outlook was becoming sinister once more. -Meanwhile the priest had groped his way back to his cell. -It had come to this Dom Claude was jealous of Quasimodo. Lost in thought, he repeated his baleful words, No one shall have her. -BOOK X -Chapter 1 - Gringoire has several bright ideas in succession in the Rue des Bernardins -Directly Gringoire had seen the turn affairs were taking, and that there was every prospect of the rope, the gallows, and various other disagreeables for the chief actors in this drama, he felt in nowise drawn to take part in it. The truands, with whom he had remained, considering them the best company in Paris the truands continued to be interested in the gipsy girl. This he judged very natural in people who, like her, had nothing but Charmolue and Torterue to look forward to, and did not caracol in the regions of the imagination as he did astride of Pegasus. He had learned from them that his bride of the broken pitcher had taken refuge in NotreCDame, and he rejoiced at it. But he was not even tempted to go and visit her there. He sometimes thought of the little goat, but that was the utmost. For the rest, he performed feats of strength during the daytime to earn a living, and at night he was engaged in elaborating a memorial against the Bishop of Paris, for he had not forgotten how the wheels of his mills had drenched him, and owed the bishop a grudge in consequence. He was also busy writing a commentary on the great work of Baudry le Rouge, Bishop of Noyon and Tournay, De Cupa Petrarum, which had inspired him with a violent taste for architecture, a love which had supplanted his passion for hermetics, of which, too, it was but a natural consequence, seeing that there is an intimate connection between hermetics and freemasonry. Gringoire had passed from the love of an idea to the love for its outward form. -He happened one day to stop near the Church of SaintCGermainClAuxerrois, at a corner of a building called the ForClvque, which was opposite another called the ForCleCRoi. To the former was attached a charming fourteenth century chapel, the chancel of which was towards the street. Gringoire was absorbed in studying its external sculpture. It was one of those moments of selfish, exclusive, and supreme enjoyment in which the artist sees nothing in all the world but art, and sees the whole world in art. Suddenly a hand was laid heavily on his shoulder. He turned round it was his former friend and master, the Archdeacon. -He stood gaping stupidly. It was long since he had seen the Archdeacon, and Dom Claude was one of those grave and intense men who invariably upset a sceptical philosophers equilibrium. -The Archdeacon kept silence for some moments, during which Gringoire found leisure to observe him more closely. He thought Dom Claude greatly altered, pallid as a winters morning, hollowCeyed, his hair nearly white. The priest was the first to break this silence: -How fares it with you, Maitre Pierre?he asked in a cold and even tone. -My health?returned Gringoire. Well, as to that, it has its ups and downs; but on the whole, I may say it is good. I am moderate in all things. You know, master, the secret, according to Hippocrates; id est: cibi, potus, somni, venus, omnia moderata sunt. Food, drink, sleep, love all in moderation. -You have no care then, Maitre Gringoire?resumed the priest, fixing Gringoire with a penetrating eye. -Faith, not I. -And what are you doing now? -You see for yourself, master; I am examining the cutting of these stones, and the style of this basCrelief. -The priest smiled faintly, but with that scornful smile which only curls one corner of the mouth. And that amuses you? -It is paradise!exclaimed Gringoire. And bending over the stone carvings with the fascinated air of a demonstrator of living phenomena For example,he said, look at this basCrelief: do you not consider its execution a marvel of skill, delicacy, and patience? Look at this small column: where would you find a capital whose leaves were more daintily entwined or more tenderly treated by the chisel? Here are three round altoCrelievos by Jean Maillevin. They are not the finest examples of that great genius; nevertheless, the childlike simplicity, the sweetness of the faces, the sportive grace of the attitudes and the draperies, and the indefinable charm which is mingled with all the imperfections, makes the little figures wonderfully airy and delicate perhaps almost too much so. You do not find that diverting? -Oh, yes,said the priest. -And if you were to see the interior of the chapel!continued the poet with his loquacious enthusiasm. Carvings everywhere leafy as the heart of a cabbage! The chancel is most devout in style and quite unique. Nowhere have I seen anything similar! -Dom Claude interrupted him: You are happy, then? -Upon my honour, yes!returned Gringoire rapturously. I began by loving women, and went on to animals; now I am in love with stones. It is quite as diverting as beasts or women, and less fickle. -The priest passed his hand across his brow. The gesture was habitual with him. Say you so? -Look you,said Gringoire, what joys are to be extracted from it!He took the priest by the arm, who yielded passively, and led him into the stair turret of the ForClEvque. Look at that stair! Every time I see it it makes me happy. The style of that flight of steps is the simplest and most rare in Paris. Each step is sloped underneath. Its beauty and its simplicity consist in the fact of the steps, which are about a foot broad, being interlaced, mortised, jointed, linked, interwoven, and fitting into one another in a manner truly both firm and elegant. -And you long for nothing? -No. -And you have no regrets? -Neither regrets nor desires. I have arranged my life to my satisfaction. -What man arranges,said Claude, circumstances may disarrange. -I am a Pyrrhonian philosopher,returned Gringoire, and I hold the equilibrium in everything. -And how do you get your living? -I still write an epopee or a tragedy now and then; but what brings me in the most is that industry in which you have already seen me engaged, master carrying a pyramid of chairs in my teeth. -A gross occupation for a philosopher. -Tis always a form of equilibrium,returned Gringoire. When one takes up an idea, one finds something of it everywhere. -I know it,answered the Archdeacon. Then after a pause he went on: Nevertheless, you are very poor? -Poor, yes; unhappy, no. -There was a clatter of horses hoofs, and the two friends saw a company of the Kings archers file past the end of the street, their lances high and an officer at their head. The cavalcade was brilliant, and the street echoed to their tread. -How you look at that officer!said Gringoire to the Archdeacon. -It is because I seem to know him. -What is his name? -I think,answered Claude, it is Ph?bus de Chateaupers. -Ph?bus! a curious name that! There is a Count of Foix called Ph?bus. I remember that a girl I once knew never swore by any other name. -Come away,said the priest, I have something to say to you. -A certain degree of agitation was perceptible under the Archdeacons glacial manner since the passing of the troop of soldiers. He started off walking, Gringoire following, accustomed to obey like all who once came under the influence of that dominating personality. They proceeded in silence till they reached the Rue des Bernardins, which was wellCnigh deserted. Here Dom Claude came to a standstill. -What have you to say to me, master?asked Gringoire. -Do you not consider,answered the Archdeacon with an air of profound reflection, that the attire of those cavaliers is handsomer than yours or mine? -Gringoire shook his head. Faith, I prefer my red and yellow cloak to those iron and steel scales. Wheres the pleasure of making a noise when you walk like the Iron Wharf in an earthquake? -Then, Gringoire, you have never envied those fine fellows in their coats of mail? -Envied them for what, Monsieur the Archdeacon? Their strength, their arms, their discipline? Nay, give me philosophy and independence in rags. Id rather be the head of a fly than the tail of a lion. -How singular!mused the priest. A fine uniform is, nevertheless, a fine thing in its way. -Gringoire seeing him immersed in thought, strolled away to admire the porch of a neighbouring house. He returned clapping his hands. -If you were less occupied with the fine habiliments of these warriors, Monsieur the Archdeacon, I would beg you to come and see this door. I have always declared that the house of the Sieur Aubry boasts the most superb entrance in the world! -Pierre Gringoire,said the Archdeacon, what have you done with the little gipsy dancing girl? -Esmeralda, you mean? You have very abrupt changes of conversation. -Was she not your wife? -Yes, by grace of a broken pitcher. It was a four years agreement. ByCtheCbye,Gringoire went on in a half bantering tone, you still think of her, then? -And you you think of her no longer? -Not much I have so many other things. Lord, how pretty the little goat was! -Did not that Bohemian girl save your life? -Pardieu thats true! -Well, then, what has become of her? what have you done with her? -I cannot tell you. I believe they hanged her. -You believe? -I am not sure. As soon as I saw there was any question of hanging I kept out of the game. -And that is all you know about her? -Stay; I was told that she had taken refuge in Notre-Dame, and that she was in safety, and Im sure Im delighted; but I was not able to discover whether the goat had escaped with herand that is all I know about it. -Then I am going to tell you more,cried Dom Claude; and his voice, till then low, deliberate, and hollow, rose to thunder. She did find sanctuary in Notre-Dame, but in three days hence the law will drag her out again, and she will be hanged at the Grve. There is a decree of Parliament. -How very disappointing,said Gringoire. In an instant the priest had resumed his cold, grave demeanour. -And who the devil,continued the poet, has taken the trouble to solicit a decree of reintegration? Why couldnt they leave the Parliament alone? What harm can it do to any one for a poor girl to take shelter under the buttresses of Notre-Dame among the swallows nests? -There are Satans in the world,replied the Archdeacon gloomily. -Well, tis a devilish bad piece of work,observed Gringoire. -So she saved your life?the priest went on after a pause. -Yes, among my good friends the vagabonds. A touch more, a shade less, and I should have been hanged. They would have been sorry for it now. -Will you then do nothing for her? -I ask nothing better, Dom Claude; but what if I bring an ugly bit of business about my ears? -What does it matter? -Matter indeed? You are very good, my dear master! I have two great works just begun. -The priest smote his forehead. Despite the calm he affected, a violent gesture from time to time betrayed his inward struggles. How is she to be saved? -Master,said Gringoire, I can give you an answer; Il padelt, which is the Turkish for God is our hope. -How is she to be saved?repeated Dom Claude, deep in thought. -It was Gringoires turn to smite his forehead. Hark you, master, I have imagination. I will find you a choice of expedients. What if we entreated the Kings mercy? -Mercy? from Louis XI? -Why not? -Go ask the tiger for his bone! -Gringoire racked his brain for fresh solutions. -Well, thenstay: how would it be to draw up a memorial from the midwives of the city declaring the girl to be pregnant? -The priests sunken eyes glared savagely. Pregnant? Rascal, knowest thou anything of such a matter? -Gringoire recoiled in alarm at his manner. He hastened to say, Oh, not I indeed! Our marriage was a regular foris maritagium. I am altogether outside of it. But at any rate, that would secure a respite. -Folly! Infamy! Hold thy peace! -You are wrong to be angry,said Gringoire reproachfully. We get a respite which does harm to nobody, and puts forty deniers parisis into the pockets of the midwives, who are poor women. -The priest was not listening. But she must be got out of there,he murmured. The decree has to be carried out within three days That Quasimodo! Women have very depraved tastes!He raised his voice. Ma?tre Pierre, I have thought it well over; there is but one means of saving her. -And what is that? For my part I can suggest nothing. -Hark you, Ma?tre Pierre; remember that you owe your life to her. I will impart my idea frankly to you. The church is watched night and day; no one is allowed to come out who has not been seen to go in. Thus you can enter. You shall come; I will take you to her. You will change clothes with her. She will take your doublet, you will take her petticoats. -So far so good,observed the philosopher. And after? -After? Why, she will go out in your clothes, and you will stay there in hers. They will hang you, perhaps, but she will be saved. -Gringoire scratched his ear with a very serious air. Now that,said he, is an idea that would never have occurred to me. -At Dom Claudes unexpected proposal, the open and benign countenance of the poet became suddenly overcast, like a smiling landscape of Italy when a nasty squall of wind drives a cloud against the sun. -Well, Gringoire, what say you to this plan? -I say that they will not hang me perhaps, but that they will hang me indubitably. -That does not concern us. -The plague it doesnt! -She saved your life. It is a debt you ought to pay. -There is many another I dont pay. -Ma?tre Pierre, this must be done.The Archdeacon spoke imperiously. -Hark you, Dom Claude,returned the poet in consternation. You cling to that idea, but you are wrong. I see no reason why I should hang instead of another. -What is there to attract you so firmly to life? -Ah, a thousand things! -What, pray? -Whatwhy, the air, the sky, the morning, the evening, moonlight, my good friends the vagabonds, our pranks with the women, the fine architecture of Paris to study, three important books to writeone of them against the bishop and his mills; oh, more than I can say. Anaxagoras said that he was in the world merely to admire the sun. And besides, I enjoy the felicity of passing the whole of my days, from morning till night, in the company of a man of genius myself, to witand that is very agreeable. -Oh, empty rattle-pate!growled the Archdeacon. And who, prithee, preserved to thee that life thou deemest so pleasant? Whose gift is it that thou art breathing the air, looking at the sky, hast still the power to divert thy feather-brained spirit with folly and nonsense? But for her, where wouldst thou be? Thou wouldst let her die, thenher through whom thou livest? Let her diethat being so lovely, so sweet, so adorablea creature necessary to the light of the world, more divine than God himself! whilst thou, half philosopher, half foolmere outline of something, a species of vegetable that imagines it walks and thinksthou wilt go on living with the life thou hast stolen from her, useless as a torch at noonday? Come, Gringoire, a little pity! be generous in thy turn; twas she that showed thee the way. -The priest spoke vehemently. Gringoire listened at first with an air of indecision; presently he was touched, and ended by making a tragic grimace which made his wan visage like that of a new-born infant with the colic. -You are in truth most pathetic,said he, wiping away a tear. Well, Ill think on ittis an odd idea of yours, that. After all,he pursued, after a moments silence, who knows; may-be they would not hang metis not every betrothal that ends in marriage. When they find me in my hiding-place thus grotesquely disguised in coif and kirtle, it is very possible they will burst out laughing. On the other hand, even if they do hang mewell, the rope is a death like any othernay, rather it is not a death like any other it is a death worthy of a sage who has swung gently all his life between the extremesa death which, like the mind of the true sceptic, is neither flesh nor fish; a death thoroughly expressive of Pyrrhonism and hesitation, which holds the mean between heaven and earth, which holds you in suspension. Tis the death of a philosopher, and to which mayhap I was predestined. It is magnificent to die as one has lived! -The priest interrupted him. So it is a bargain, then? -When alls said and done,pursued Gringoire with exaltation, what is death? An uncomfortable momenta tollgatethe transit from little to nothing. Some one having asked Cercidas of Megalopolis whether he could die willingly, he replied, Wherefore not? for after my death I should see those great men: Pythagoras among the philosophers, Hecat?us among the historians, Homer among the poets, Olympus among the musicians. -The Archdeacon held out his hand. It is settled, then? You will come to-morrow? -This action brought Gringoire down to the realities. -Faith no!said he in the tone of a man who awakens. -Let myself be hanged?tis too absurd! I will not. -God be with you, then!and the Archdeacon muttered between his teeth, We shall meet again! -I have no desire to meet that devil of a man again,thought Gringoire. He ran after Dom Claude. Hark you, Monsieur the Archdeacon, no offence between old friends! You are interested in this girlmy wife I meanthats very well. You have devised a stratagem for getting her safely out of Notre-Dame, but your plan is highly unpleasant for me, Gringoire. If I only had another to suggest!Let me tell you that a most luminous inspiration has this instant come to me. How if I had a practicable scheme for extricating her from this tight place without exposing my own neck to the slightest danger of a slip-knot, what would you say? Would not that suffice you? Is it absolutely necessary that I should be hanged to satisfy you? -The priest was tearing at the buttons of his soutane with impatience. Oh, babbling stream of words! Out with thy plan! -Yes,said Gringoire, speaking to himself and rubbing his nose with his forefinger in sign of deep cogitation; thats it! The vagabonds are good-hearted fellows! The tribe of Egypt loves her. They will rise at a word. Nothing easier. A surpriseand under cover of the disorder, carry her off perfectly easily! This very next night. Nothing would please them better. -The planspeak!said the priest, shaking him. -Gringoire turned to him majestically. -Let me be! see you not that I am composing?He ruminated again for a few moments, then began to clap his hands at his thought. Admirable!he cried, an assured success! -The plan!repeated Claude, enraged. -Gringoire was radiant. Hist!said he, let me tell it you in a whisper. Tis a counterplot thats really brilliant, and will get us all clear out of the affair. Pardieu! you must admit that Im no fool. -He stopped short. Ah, but the little goatis she with the girl? -Yesyesdevil take thee! go on! -They would hang her too, would they not? -Whats that to me? -Yes, they would hang her. They hanged a sow last month, sure enough. The hangman likes thathe eats the beast afterward. Hang my pretty Djali! Poor sweet lamb! -A murrain on thee!cried Dom Claude. Tis thou art the hangman. What plan for saving her hast thou found, rascal? Must thou be delivered of thy scheme with the forceps? -Gently, master. This is it.Gringoire bent to the Archdeacons ear and spoke very low, casting an anxious glance up and down the street, in which, however, there was not a soul to be seen. When he had finished, Dom Claude touched his hand and said coldly: Tis well. Till to-morrow, then. -Till to-morrow,repeated Gringoire, and while the Archdeacon retreated in one direction, he went off in the other, murmuring to himself: This is a nice business, M. Pierre Gringoire! Never mind, its not to say because ones of small account one need be frightened at a great undertaking. Biton carried a great bull on his shoulders; wagtails and linnets cross the ocean. -Chapter 2 - Turn Vagabond -The Archdeacon, on returning to the cloister, found his brother, Jehan of the Mill, watching for him at the door of his cell, having whiled away the tediousness of waiting by drawing on the wall with a piece of charcoal a profile portrait of his elder brother enriched by a nose of preposterous dimensions. -Dom Claude scarcely glanced at his brother. He had other things to think of. That laughing, scampish face, whose beams had so often lifted the gloom from the sombre countenance of the priest, had now no power to dissipate the mists that gathered ever more thickly over that festering, mephitic, stagnant soul. -Brother,Jehan began timidly, I have come to see you. -The Archdeacon did not even glance at him. Well? -Brother,continued the little hypocrite, you are so good to me, and you bestow upon me such excellent advice, that I always come back to you. -What further? -Alas, brother, you were very right when you said to me: Jehan! Jehan! cessat doctorum doctrina, discipulorum disciplina. Jehan, be staid; Jehan, be studious; Jehan, spend not thy nights outside the college without lawful occasion and leave of the masters. Come not to blows with the Picardsnoli, Joannes, verbe rare Picardos. Lie not rotting like an unlettered assquasi asinus illiteratusamong the straw of the schools. Jehan, let thyself be chastised at the discretion of the master. Jehan, go every evening to chapel and sing an anthem with verse and prayer in praise of Our Lady the Virgin Mary. Alas! how excellent was that advice! -And then? -Brother, you see before you a guilty wretch, a miscreant, a profligate, a monster! My dear brother, Jehan has used your counsel as mere straw and dung to be trodden under foot. Well am I chastised for it, and the heavenly Father is extraordinarily just. So long as I had money I spent it in feasting, folly, and profligacy. Ah, how hideous and vile is the back view of debauchery compared with the smiling countenance she faces us with! Now I have not a single sou left; Ive sold my coverlet, my shirt, and my towelno merry life for me any longer! The fair taper is extinguished, and nothing remains to me but its villainous snuff that stinks in my nostrils. The girls make mock of me. I drink water. I am harassed by remorse and creditors. -The end?said the Archdeacon. -Ah, best of brothers, I would fain lead a better life. I come to you full of contrition. I am penitent. I acknowledge my sins. I beat my breast with heavy blows. You are very right to desire that I should one day become a licentiate and sub-monitor of the Collge de Torchi. I now feel a remarkable vocation for that office. But I have no more ink leftI shall be obliged to buy some; I have no pens left I must buy some; no more paper, no booksI must buy them. For that purpose I am sorely in need of the financial wherewithal. And I come to you, my brother, with a heart full of contrition. -Is that all? -Yes,said the scholar. A little money. -I have none. -The scholar assumed an air of gravity and resolution: Very good, brother, then I am sorry to have to inform you that I have received from other quarters very advantageous offers and proposals. You will not give me any money? No? In that case I shall turn Vagabond.And with this portentous word he adopted the mien of an Ajax awaiting the lightning. -The Archdeacon answered, unmoved: -Then turn Vagabond. -Jehan made him a profound bow, and descended the cloister stair-case, whistling. -As he passed through the court-yard of the cloister under his brothers window, he heard that window open, looked up, and saw the Archdeacons stern countenance leaning out of it. Get thee to the devil!called Dom Claude; here is the last money thou shalt have of me. -So saying, the priest tossed down a purse to Jehan, which raised a large bump on his forehead, and with which he set off, at once angry and delighted, like a dog that has been pelted with marrow-bones. -Chapter 3 - Vive La Joie -The reader may perhaps remember that a portion of the Court of Miracles was enclosed by the ancient wall of the city, a good many towers of which were beginning at that time to fall into decay. One of these towers had been converted by the truands into a place of entertainment, with a tavern in the basement, and the rest in the upper storeys. This tower was the most animated, and consequently the most hideous, spot in the whole Vagabond quartera monstrous hive, buzzing day and night. At night, when the rest of the rabble were asleepwhen not a lighted window was to be seen in the squalid fronts of the houses round the Place, when all sound had ceased in the innumerable tenements with their swarms of thieves, loose women, stolen or bastard children the joyous tower could always be distinguished by the uproar that issued from it, and by the crimson glow of light streaming out from the loopholes, the windows, the fissures in the gaping walls, escaping, as it were, from every pore. -The tavern, as we have said, was in the basement. The descent to it was through a low door and down a steep, narrow stair. Over the door, by way of sign, hung an extraordinary daub representing new-coined sols and dead fowls, with the punning legend underneath, Aux sonneurs1 pour les trpasss!The ringers for the dead. -One evening, when the curfew was ringing from all the steeples of Paris, the sergeants of the watch, could they have entered the redoubtable Court of Miracles, might have remarked that a greater hubbub than usual was going on in the tavern of the Vagabonds; that they were drinking deeper and swearing harder. Without, in the Place, were a number of groups conversing in low tones, as when some great plot is brewing, and here and there some fellow crouched down and sharpened a villainous iron blade on a flag-stone. -Meanwhile, in the tavern itself, wine and gambling formed so strong a diversion to the ideas that occupied the Vagabonds, that it would have been difficult to gather from the conversation of the drinkers what the matter was which so engaged them. Only they wore a gayer air than usual, and every one of them had some weapon or other gleaming between his kneesa pruning-hook, an axe, a broadsword, or the crook of some ancient blunderbuss. -The hall, which was circular in form, was very spacious; but the tables were so crowded together and the drinkers so numerous, that the whole contents of the tavernmen, women, benches, tankards, drinkers, sleepers, gamblers, the able-bodied and the crippledseemed thrown pell-mell together, with about as much order and harmony as a heap of oyster-shells. A few tallow candles guttered on the table; but the real source of light to the tavern, that which sustained in the cabaret the character of the chandelier in an opera-house, was the fire. This cellar was so damp that the fire was never allowed to go out, even in the height of summer; an immense fire-place with a carved chimney-piece, and crowded with heavy andirons and cooking utensils, contained one of those huge fires of wood and turf which in a village street at night cast the deep red glow of the forge windows on the opposite wall. A great dog, gravely seated in the ashes, was turning a spit hung with meat. -In spite of the prevailing confusion, after the first glance three principal groups might be singled out, pressing round the several personages already known to the reader. One of these personages, fantastically bedizened with many an Oriental gaud, was Mathias Hungadi Spicali, Duke of Egypt and Bohemia. The old rogue was seated cross-legged on a table, his finger upraised, exhibiting in a loud voice his skill in white and black magic to many an open-mouthed face that surrounded him. -Another crowd was gathered thick round our old friend the King of Tunis, armed to the teeth. Clopin Trouillefou, with a very serious mien and in a low voice, was superintending the ransacking of an enormous cask full of arms staved open before him and disgorging a profusion of axes, swords, firelocks, coats of mail, lance and pike heads, crossbows and arrows, like apples and grapes from a cornucopia. Each one took something from the heapone a morion, another a rapier, a third a cross-hilted dagger. The very children were arming, and even the worst cripples, mere torsos of men, all barbed and cuirassed, were crawling about among the legs of the drinkers like so many great beetles. -And lastly, a third audiencemuch the noisiest, most jovial, and numerous of the lotcrowded the benches and tables, listening to the haranguing and swearing of a flutelike voice which proceeded from a figure dressed in a complete suit of heavy armour from casque to spurs. The individual thus trussed up in full panoply was so buried under his warlike accoutrements that nothing of his person was visible but an impudent tip-tilted nose, a lock of golden hair, a rosy mouth, and a pair of bold blue eyes. His belt bristled with daggers and poniards, a large sword hung at one side, a rusty cross-bow at the other, a vast jug of wine stood before him, and in his right arm he held a strapping wench with uncovered bosom. Every mouth in his neighbourhood was laughing, drinking, swearing. -Add to these twenty minor groups; the serving men and women running to and fro with wine and beer-cans on their heads, the players absorbed in the various games of hazardbilles (a primitive form of billiards), dice, cards, backgammon, the intensely exciting tringlet(a form of spilikins), quarrels in one corner, kisses in anotherand some idea may be formed of the scene, over which flickered the light of the great blazing fire, setting a thousand grotesque and enormous shadows dancing on the tavern walls. -As to the noisethe place might have been the inside of a bell in full peal, while any intervals that might occur in the hubbub were filled by the spluttering of the dripping-pan in front of the fire. -In the midst of all this uproar, on a bench inside the fireplace, a philosopher sat and meditated, with his feet in the ashes and his eyes fixed on the blaze. It was Pierre Gringoire. -Now, then, look alive, arm yourselveswe march in an hour!said Clopin Trouillefou to his rascals. -A girl sang a snatch of song: -Father and mother dear, good-night; -The last to go put out the light. -Two card-players were disputing. Knave!cried the reddest-faced of the two, shaking his fist at the other, Ill so mark thee thou mightest take the place of knave of clubs in our lord the Kings own pack of cards! -Ouf!roared one, whose nasal drawl betrayed him as a Norman; we are packed together here like the saints of Caillouville! -Children,said the Duke of Egypt to his audience in a falsetto voice, the witches of France go to the Sabbaths without ointment, or broomsticks, or any other mount, by a few magic words only. The witches of Italy have always a goat in readiness at the door. All are bound to go up the chimney. -The voice of the young scamp armed cap--pie dominated the hubbub. -No?l! No?l!he cried. My first day in armour! A Vagabond! Im a Vagabond, body of Christ! pour me some wine! My friends, my name is Jehan Frollo of the Mill, and Im a gentleman. Its my opinion that if the Almighty were a man-at-arms hed turn robber. Brothers, we are bound on a great expedition. We are doughty men. Lay siege to the church, break in the doors, bring out the maid, save her from the judges, save her from the priests, dismantle the cloister, burn the bishop in his housewell do all this in less time than it takes a burgomaster to eat a mouthful of soup. Our cause is a righteous onewe loot Notre-Dame, and there you are! Well hang Quasimodo. Are you acquainted with Quasimodo, fair ladies? Have you seen him snorting on the back of the big bell on a day of high festival? Corne du Pre! tis a grand sightyoud say it was a devil astride a gaping maw. Hark ye, my friends; I am a truand to the bottom of my heart, I am Argotier to the soul, Im a born Cagou. I was very rich, but Ive spent all I had. My mother wanted to make me an officer, my father a subdeacon, my aunt a criminal councillor, my grandmother a protonotary, but I made myself a Vagabond. I told my father so, and he spat his curse in my face; my mother, the good old lady, fell to weeping and spluttering like the log in that fire-place. So hey for a merry life! Im a whole madhouse in myself. Landlady, my duck, some more wineIve got some money left yet, but no more of that Suresnes, it rasps my throat. Why, corb?uf, its like gargling with a basket! -The crowd received his every utterance with yells of laughter, and seeing that the uproar was increasing round him, the scholar cried: O glorious uproar! Populi debacchantis -populosa debacchatio!and set off singing, his eyes swimming in apparent ecstasy, in the tone of a canon chanting vespers: Qu? cantica! qu? organa! qu? cantilen?! qu? melodi? his sine fine decantantur! sonant melliflua hymnorum or gana, suavissima angelorum melodia, cantica canticorum mira.2 -He broke off. Hey theredevils own landladygive me some supper! -There was a moment almost of silence, during which the strident voice of the Duke of Egypt was heard instructing his Bohemians: -The weasel goes by the name of Aduine, the fox is Bluefoot or Woodranger, the wolf, Grayfoot or Giltfoot, the bear, Old Man, or Grandfather. The cap of a gnome renders one invisible and makes one see invisible things. When a toad is baptized it should be clad in velvetred or blacka bell at its neck, a bell on its foot. The godfather holds the head, the godmother the hinder parts. It is the demon Sidragasum that has the power of making girls dance naked. -By the mass!broke in Jehan, I would I were a demon Sidragasum. -All this time the truands had been steadily arming themselves at the other side of the tavern, whispering to one another. -Poor Esmeralda!said a gipsy. She is our sister. We must get her out of that! -Is she there still in Notre-Dame?asked a Jewishlooking huckster. -Yes, by God! -Well, comrades,exclaimed the huckster, to Notre-Dame, then! All the more because in the chapel of Saints Frol and Ferrution there are two statues, one of Saint-John the Baptist and the other of Saint-Anthony, both of pure gold, weighing together seven gold marks and fifteen esterlins,3 and the pedestals of silver-gilt weigh seventeen marks five ounces. I know itI am a goldsmith. -Here they served Jehans supper. He lolled on the bosom of the girl beside him. By Saint-Voult-de-Lucques, called familiarly Saint-Goguelu, now Im perfectly happy!he cried. Here in front of me I see a blockhead with the beardless face of an archduke. On my left is another with teeth so long they hide his chin. Body of Mahomet! Comrade! thou hast all the appearance of a draper, and hast the effrontery to come and sit by me! I am noble, my friend, and trade is incompatible with nobility. Get thee farther off. Hol, you there! no fighting! How now! Baptiste Croque-Oison, wouldst risk that splendid nose of thine under the gross fists of yonder bumpkin! Imbecile! Non cuiquam datum est habere nasum.4 Truly thou art divine, Jacqueline Rouge-Oreille! pity tis thou hast no hair. Hol! My names Jehan Frollo, and my brothers an archdeaconmay the devil fly away with him! Every word I tell you is the truth. By turning Vagabond, I have cheerfully renounced the half of a house situate in paradise promised me by my brotherdimidem donum in paradisoI quote the very words. Ive a property in the Rue Tirechappe, and all the women run after meas true as its true that Saint-Eligius was an excellent goldsmith, and that the five trades of the good city of Paris are the tanners, the leather-dressers, the baldrick-makers, the purse-makers, and the leather-scourers, and that Saint-Laurence was burned with hot egg-shells. I swear to you, comrades, -For a full year Ill taste no wine -If this be any lie of mine! -My charmer, tis moonlight; look through that loophole how the wind rumples the cloudsjust as I do with thy kerchief. Girls, snuff the children and the candles. Christ and Mahomet! what am I eating now? Hey there, old jade! the hairs that are missing from the heads of thy trulls we find in the omelets! Hark ye, old lady, I prefer my omelets bald. May the devil flatten thy nose! A fine tavern of Beelzebub, in sooth, where the wenches comb themselves with the forks! -With which he smashed his plate on the floor and began singing in an ear-splitting voice: -By the blood of Christ, -I lay no store -By faith or law, -Neither hearth nor home -Do I call my own, -Nor God, -Nor King! -By this time Clopin Trouillefou had finished distributing his arms. Approaching Gringoire, who seemed plunged in profound reverie, his feet on a log: -Friend Pierre,said the King of Tunis, what the devil art thinking about? -Gringoire turned to him with a melancholy smile. I love the fire, my dear sir. Not for the trivial reason that it warms our feet and cooks our soup, but because it throws out sparks. Sometimes I pass whole hours watching the sparks. I discover a host of things in those stars that sprinkle the dark background of the fire-place. Those stars are worlds. -The fiend take me if I understand thee,said the Vagabond. Dost thou know whats oclock? -I do not,answered Gringoire. Clopin went to the Duke of Egypt. -Comrade Mathias, the moment is ill-chosen. They say King Louis is in Paris. -All the more need for getting our sister out of his clutches,answered the old Bohemian. -You speak like a man, Mathias,returned the King of Tunis. Besides, it will be an easy matter. Theres no resistance to fear in the church. The priests are so many hares, and we are in full force. The men of the Parliament will be finely balked to-morrow when they come to fetch her! By the bowels of the Pope, they shall not hang the pretty creature! -Clopin then left the tavern. -In the meantime Jehan was shouting hoarsely: I drink I eatIm drunkI am Jupiter! Ah, Pierre l Assommeur, if thou glarest at me again in that manner, Ill dust thy nose with my fist! -Gringoire, on his part, aroused from his meditations, was contemplating the wild scene of license and uproar around him, while he murmured to himself: Luxuriosa res vinum et tumultuosa ebrietas.5 Ah, how wise am I to eschew drinking, and how excellent is the saying of Saint-Benedict: Vinum apostatare facit etiam sapientes!6 -At this moment Clopin returned and shouted in a voice of thunder, Midnight! -The word acted on the truands like the order to mount on a regiment, and the entire bandmen, women, and childrenpoured out of the tavern with a great clatter of arms and iron. The moon was obscured. The Court of Miracles lay in utter darknessnot a single light was to be seen, but it was far from being deserted. A great crowd of men and women stood in the Place talking to one another in low voices. There was a continuous deep hum, and many a weapon flashed in the gloom. -Clopin mounted on a great stone. To your ranks, Argot!cried he. To your ranks, Egypt! To your ranks, Galilee! -A movement ran through the darkness. The vast multitude seemed to be forming in columns. After a few minutes the King of Tunis once more lifted up his voice: -Now, then, silence on the march through Paris! The password is Dagger in pouch. Torches not to be lighted till we reach Notre-Dame! March! -Ten minutes later the horsemen of the night-watch were fleeing in terror before a long procession, black and silent, pouring down towards the Pont-au-Change through the tortuous streets that run in every direction through the dense quarter of the Halles. -______________________ -1 Slang term for ready money, hard cash. -2 What chants! what instruments! what songs and melodies without end are sung here! Hymns from mellifluous pipes are sounding, sweetest of angels melodies, the most wonderful song of all songs. -3 Obsolete goldsmith weight of 28 4/5 grains. -4 It is not given to every one to have a nose. -5 A dissolute thing is wine and leads to noisy intoxication. -6 The avoiding of wine also makes a man wise. -Chapter 4 - An Awkward Friend -Quasimodo on that night was not asleep. He had just gone his last round through the church. He had failed to remark that at the moment when he was closing the doors the Archdeacon had passed near him and evinced some annoyance at seeing him bolt and padlock with care the enormous iron bars which gave the wide doors the solidity of a wall. Dom Claude seemed even more preoccupied than usual. Moreover, since the nocturnal adventure in the cell, he treated Quasimodo with constant unkindness; but in vain he used him harshly, sometimes even striking himnothing could shake the submissive patience, the devoted resignation of the faithful bell-ringer. From the Archdeacon he would endure anythingabuse, threats, blowswithout a murmur of reproach, without even a sigh of complaint. The utmost that he did was to follow Dom Claude with an anxious eye if he mounted the stair of the tower; but the Archdeacon had of himself abstained from appearing again before the gipsy girl. -That night, then, Quasimodo, after a glance at his poor forsaken bells, Jacqueline, Marie, Thibauld, had ascended to the top of the northern tower, and there, after setting down his dark-lantern on the leads, he fell to contemplating Paris. The night, as we have said, was very dark. Paris, which, speaking broadly, was not lighted at all at that period, presented to the eye a confused mass of black blots, cut here and there by the pale windings of the river. Quasimodo saw not a light except in the window of a distant edifice, whose vague and sombre outline was distinguishable high above the roofs in the direction of the Porte Saint-Antoine. Here, too, some one kept vigil. -While his eye thus lingered over the dark and misty scene, the bell-ringer felt an indescribable sense of anxiety rising within him. For several days he had been on the watch. He had constantly noticed men of sinister aspect loitering round the church and never taking their eyes off the gipsy girls hiding-place. He feared lest some plot should be hatching against the unfortunate refugee. He conceived her to be an object of popular hatred, as he was himself, and that something might very well be going to happen in the immediate future. Thus he remained on his tower on the lookoutRevant dans son revoir Musing in his musery as Rabelais says, his eye by turns on the cell and on Paris, keeping safe watch, like a trusty dog, with a thousand suspicions in his mind. -All at once, while he was reconnoitring the great city with that solitary eye which nature, as if by way of compensation, had made so piercing that it almost supplied the deficiency of other organs in Quasimodo, it struck him that there was something unusual in the appearance of the outline of the quay of the Veille Pelleterie, that there was some movement at this point, that the line of the parapet which stood out black against the whiteness of the water was not straight and still like that of the other quays, but that it appeared to undulate like the waves of a river or the heads of a crowd in motion. -He thought this very peculiar. He redoubled his attention. The movement appeared to be coming towards the city not a light, however. It lasted some time on the quay, and then flowed away by degrees, as if whatever was passing along was entering the interior of the island; then it ceased altogether, and the line of the quay returned to its wonted straightness and immobility. -Just as Quasimodo was exhausting himself in conjectures, it seemed to him that the movement was reappearing in the Rue du Parvis, which runs into the city in a straight line with the front of Notre-Dame. At last, despite the great darkness, he could descry the head of a column issuing from that street, and the next instant a crowd spreading out into the square, of which he could distinguish nothing further than that it was a crowd. -It was a fear-compelling spectacle. No doubt this strange procession, which seemed so anxious to cloak itself under the profound darkness, preserved a silence no less profound. Still, some sound must have escaped from it, were it only the tramp of feet. But even this sound did not reach the deaf hunchback, and the great multitude, which he could only dimly see, but which he heard not at all, moving so near him, seemed to him like an assemblage of the deadmute, ghostly shapes, hovering in a mistshadows in a shade. -Then his former fears returned; the idea of an attempt against the gipsy girl presented itself once more to his mind. He had a vague premonition of some violent situation approaching. At this critical moment he held counsel with himself, reasoning with greater acumen and promptness than would have been expected from so ill-organized a brain. Should he awaken the gipsy girl?help her to escape? Which way? The streets were blocked, the church was backed by the riverno boatno egress. There remained but one thing thereforeto face death on the threshold of Notre-Dame; to hold them off at least until assistance came, supposing there were any to come, and not to disturb the slumbers of Esmeralda. The unhappy girl would always be awakened early enough to die. This resolution once taken, he proceeded to observe the enemy with greater calmness. -The crowd in the Parvis appeared to be increasing momentarily; though, seeing that the windows of the streets and the Place remained closed, he concluded that they could not be making much noise. Suddenly a light shone out, and in an instant seven or eight torches were waving above the heads, tossing their plumes of flame through the darkness. By their light Quasimodo had a clear vision of an appalling band of tatterdemalionsmen and womenflocking into the Parvis, armed with scythes, pikes, pruning-forks, partisanstheir thousand blades glittering as they caught the fitful light and here and there black pitchforks furnishing horns to these hideous visages. He had a confused remembrance of that populace, and thought to recognise in them the crowd which but a few months before had acclaimed him Pope of Fools. A man holding a torch in one hand and a birch rod in the other was mounted on a corner post and apparently haranguing the multitude, and at the same time the ghostly army performed some evolutions as if taking up a position round the church. Quasimodo picked up his lantern and descended to the platform between the towers to observe more closely and deliberate on the means of defence. -Arrived in front of the great door of Notre-Dame, Clopin Trouillefou had in fact drawn up his troops in battle array. Though anticipating no resistance, yet, like a prudent general, he determined to preserve so much order as would, in case of need, enable him to face a sudden attack of the watch or the city guard. Accordingly, he had so disposed his brigade that, seen from above and at a distance, it might have been taken for the Roman triangle at the battle of Ecnoma, the boars head of Alexander, or the famous wedge of Gustavus Adolphus. The base of this triangle ran along the back of the Place in such a manner as to bar the Rue du Parvis, one side looked towards the H?tel-Dieu, the other towards the Rue Saint-Pierre aux B?ufs. Clopin Trouillefou had posted himself at the point with the Duke of Egypt, our friend Jehan, and the boldest of the beggar tribe. -An enterprise such as the truands were now attempting against Notre-Dame was by no means an uncommon occurrence in the Middle Ages. What we now call police did not then exist. In the populous cities, particularly in the capitals, there was no united central power regulating the whole. Feudalism had shaped these great municipalities after an absurd fashion. A city was a collection of innumerable seigneuries, cutting it up into divisions of all shapes and sizes; hence its crowd of contradictory police establishments, or rather no police at all. In Paris, for instance, independently of the hundred and forty-one feudal lords claiming manorial dues, there were twenty-five claiming justiciary and manorial rights, from the Bishop of Paris, who possessed a hundred and five streets, to the Prior of Notre-Dame des Champs, who had only four. All these feudal justiciaries recognised only nominally the paramount authority of the King. All exercised right of highway, all were their own masters. Louis XI that indefatigable workman, who commenced on so large a scale the demolition of the feudal edifice, continued by Richelieu and Louis XIV to the advantage of royalty, and completed by Mirabeau to the peopleLouis XI had done his utmost to break up this network of seigneuries which covered Paris, by casting violently athwart it two or three ordinances of general police. Thus, in 1465, we find the inhabitants ordered to put lighted candles in their windows at nightfall, and to shut up their dogs on pain of the halter; in the same year, the order to bar the streets at night with iron chains, and the prohibition against their carrying daggers or any other offensive weapon in the streets at night. But in a short time all these attempts at municipal legislation fell into disuse; the citizens let the candles at their windows be extinguished by the wind and their dogs roam at large; the iron chains were only stretched across the street in case of siege, and the prohibition against carrying weapons brought about no other changes than converting the Rue Coupe-Gueule into Coupe-Gorge; which, to be sure, is a clear evidence of progress. The old framework of the feudal jurisdictions remained standingan immense accumulation of bailiwicks and seigneuries, crossing one another in all directions through the length and breadth of the city, embarrassing, entangling, overlapping one anothera useless thicket of watches, counter-watches, and out-watches, through the very midst of which stalked brigandage, rapine, and sedition, sword in hand. Under such condition of disorder, therefore, it excited no very great remark if a part of the populace laid violent hands on a palace, a mansion, or any ordinary dwelling-house in the most populated quarters of the city. In most cases the neighbours did not interfere in the matter unless the plundering extended to themselves. They stopped their ears to the report of the musketry, closed their shutters, barricaded their doors, and let the struggle exhaust itself with or without the assistance of the watch, and the next day it would be quietly said in Paris: night tienne Barbettes house was broken into, or The Marshal de Clermont was attacked, etc. Hence, not only the royal residences, the Louvre, the Palais, the Bastille, the Tournelles, but the mansions of the nobility, such as the Petit-Bourbon, the H?tel de Sens, the H?tel dAngoulme, and so on, had their battlemented walls and their fortified turrets over the entrances. The churches were protected by their sanctity. Some of them, nevertheless among which was not Notre-Damewere fortified. The Abbey of Saint-Germain-des-Prs was castellated like a baronial mansion, and more copper had been used there for bombards than for bells. These fortifications were still to be seen in 1610; now scarcely the church remains. -But to return to Notre-Dame. -The first arrangements completedand it must be said, to the honour of the truand discipline, that Clopins orders were carried out in silence and with admirable precision the worthy leader mounted the parapet of the Parvis, turned his face to Notre-Dame, and raising his harsh and churlish voice while he shook his torchthe light of which flaring in the wind and veiled at intervals by its own smoke, made the dark front of the Cathedral vanish and reappear by turns -Unto thee, he cried, Louis de Beaumont, Bishop of Paris, Councillor in the Court of Parliament, thus say I, Clopin Trouillefou, King of Tunis, Grand Co?sre, Prince of Argot, Bishop of the Fools: Our sister, falsely condemned for witchcraft, has taken refuge in thy church. Thou art bound to accord her shelter and safeguard; but now the Parliament designs to take her thence, and thou consentest thereunto, so that she would be hanged to-morrow at the Grve if God and the truands were not at hand. We come to thee, then, Bishop. If thy church is sacred, our sister is so too; if our sister is not sacred, neither is thy church. Wherefore we summon thee to give up the maid if thou wouldst save thy church, or we will take the maid ourselves and plunder the church: which will most certainly happen. In token whereof I here set up my banner. And so God help thee, Bishop of Paris! -Unfortunately Quasimodo could not hear these words, which were delivered with a sort of savage and morose dignity. A Vagabond handed Clopin his banner, which he gravely planted between two paving-stones. It was a pitchfork on which hung a gory piece of carrion. -This done, the King of Tunis turned about and cast his eye over his army, a ferocious multitude whose eyes gleamed almost as savagely as their pikes. After a moments pause Forward, lads! he cried. To your work, housebreakers! -Thirty thick-set, strong-limbed men with hammers, pincers, and iron crowbars on their shoulders, stepped from the ranks. They advanced towards the main entrance of the church, ascended the steps, and immediately set to work on the door with pincers and levers. A large party of truands followed them to assist or look on, so that the whole flight of eleven steps was crowded with them. -The door, however, held firm. The devil! but shes hard and headstrong! said one. Shes old, and her gristles tough! said another. Courage, comrades! said Clopin. I wager my head against a slipper that youll have burst the door, got the maid, and stripped the high altar before ever theres a beadle of them all awake. ThereI believe the locks going. -Clopin was interrupted by a frightful noise which at that moment resounded behind him. He turned round. An enormous beam had just fallen from on high, crushing a dozen truands on the steps of the church and rebounding on to the pavement with the noise of a piece of artillery, breaking here and there the legs of others among the Vagabond crowd, which fled in all directions with cries of terror. In a trice the enclosure of the Parvis was empty. The door-breakers, though protected by the deep arches of the doorway, abandoned it, and Clopin himself fell back to a respectful distance from the church. -Tte-b?uf! I had a narrow escape! cried Jehan. I felt the wind of it; but Pierre the Feller is felled at last. -It would be impossible to describe the mingled astonishment and alarm that fell with this beam upon the bandit crew. They remained for a few minutes gazing open-mouthed into the air, in greater consternation at this piece of wood than at twenty thousand Kings archers. -Satan! growled the Duke of Egypt, but this smells of magic! -Its the moon thats thrown this log at us, said Andry le Rouge. -Thats it, returned Francois Chanteprune, for they say the moons the friend of the Virgin. -A thousand popes! cried Clopin, youre a parcel of dunderheads, the whole lot of you! But he knew no better than they how to account for the beam, for nothing was perceptible on the front of the building, to the top of which the light of the torches could not reach. The ponderous beam lay in the middle of the Parvis, and the groans of the poor wretches could be heard who had received its first shock and had been almost cut in two on the sharp edges of the stone steps. -At last the King of Tunis, his first surprise past, discovered an explanation which seemed plausible to his fellows. -Gueule-Dieu! Can the clergy be making a defence? If that be so, thento the sack! to the sack! -To the sack! yelled the band with a furious hurrah, and discharged a volley of cross-bows and arquebuses against the fa?ade of the Cathedral. -Roused by the detonation, the peaceable inhabitants of the surrounding houses awoke, several windows opened, and nightcapped heads appeared at the casements. -Fire at the windows! shouted Clopin. The shutters closed on the instant, and the poor citizens, who had only had time to catch a bewildered glimpse of the scene of glare and tumult, returned in a cold perspiration of fright to their wives, wondering whether the witches now held their Sabbaths in the Parvis of Notre-Dame, or whether it was another assault by the Burgundians, as in 64. The men thought of robbery; the wives, of rape; and all trembled. -To the sack! repeated the Argotiers; but they did not venture closer. They looked from the Cathedral to the mysterious beam. The beam lay perfectly still, the church preserved its peaceful, solitary aspect; but something froze the courage of the Vagabonds. -To your work, lads! cried Trouillefou. Comeforce the door! -Nobody stirred a step. -Beard and belly! exclaimed Clopin; why, here are men afraid of a rafter! -An old Vagabond now addressed him: -Captain, its not the rafter we mind, tis the door. Thats all covered with bars of iron. The picks are no good against it. -What do you want, then, to burst it open? inquired Clopin. -Why, we want a battering-ram. -The King of Tunis ran boldly to the formidable piece of timber and set his foot on it. Heres one! cried he, and the reverend canons themselves have sent it you. Then, making a mock salute to the Cathedral, My thanks to you, canons! he added. -This piece of bravado had excellent effectthe spell of the miraculous rafter was broken. The truands plucked up their courage, and soon the heavy beam, lifted like a feather by two hundred vigorous arms, was driven furiously against the great door which they had already endeavoured in vain to loosen. Seen thus in the dim light cast over the Place by the scattered torches of the truands, the vast beam borne along by that crowd of men and pointed against the church looked like some miraculous animal with innumerable legs charging head foremost at the stone giantess. -As the beam struck the half-metal door it droned like an enormous drum. The door did not give, but the Cathedral shook from top to bottom, and rumbling echoes woke in its deepest depths. At the same moment a shower of great stones began to fall from the upper part of the facade on to the assailants. -Diable! cried Jehan, are the towers shaking down their balustrades upon us? -But the impulse had been given. The King of Tunis stuck to his assertion that it was the Bishop acting on the defensive, and they only battered the door the more furiously for the stones that fractured the skulls right and left. -It was certainly curious that these stones fell one by one, but they followed quickly on one another. The Argotiers always felt two of them at onceone against their legs, the other on their heads. There were few that missed their mark, and already a heap of dead and wounded, bleeding and panting, lay thick under the feet of the assailants, who, now grown furious, renewed their numbers every moment. The long beam continued to batter the door at regular intervals like the strokes of a bell, the stones to rain down, and the door to groan. -The reader will doubtless have guessed ere this that the unexpected resistance which so exasperated the Vagabonds proceeded from Quasimodo. -Accident had unfortunately favoured the devoted hunchback. When he had descended to the platform between the towers, his ideas were in a state of chaos. He had run to and fro along the gallery for some minutes like one demented, looking down upon the compact mass of the beggars ready to rush the church, and calling upon God or the devil to save the gipsy girl. He thought of ascending the southern steeple and sounding the tocsin, but before he could have got the bell in motion, before the loud voice of Marie could have sent forth a single stroke, there would have been time to burst in the door ten times over. This was the instant at which the Vagabonds advanced with their lock-breaking instruments. What was to be done? -Suddenly he recollected that masons had been at work all day repairing the wall, the wood-work, and the roofing of the southern tower. This was a flash of light to him. The wall was of stone, the roofing of lead, the rafters of wood, and so enormous and close-packed that it was called the forest. -Quasimodo flew to this tower. The lower chambers in effect were full of building materialspiles of stone blocks, sheets of lead in rolls, bundles of laths, strong beams already shaped by the saw, several rubbish heapsa complete arsenal. -Time pressedthe levers and hammers were at work below. With a strength multiplied tenfold by the consciousness of danger, he lifted an end of one of the beamsthe longest and heaviest of all. He managed to push it through one of the loopholes; then, laying hold of it again outside the tower, he pushed it over the outer corner of the balustrade surrounding the platform and let it drop into the abyss below. In this fall of a hundred and sixty feet the enormous beam grazing the wall and breaking the sculptured figures turned several times on its own axis, like the sail of a windmill going round of itself through space. Finally it reached the ground, a horrid cry went up, and the black piece of timber rebounded on the pavement, like a serpent rearing. -Quasimodo saw the enemy scattered by the fall of the beam like ashes by the breath of a child; and while they fixed their superstitious gaze on this immense log fallen from the skies, and were peppering the stone saints of the doorway with a volley of bolts and bullets, Quasimodo was silently piling up stones and rubbish, and even the masons bags of tools, upon the edge of the balustrade from which he had already hurled the beam. -Accordingly, no sooner did they begin to batter the door, than the showers of stone blocks began to fall, till they thought the church must be shaking itself to pieces on the top of them. -Any one who could have seen Quasimodo at that moment would have been appalled. Besides the missiles which he had piled up on the balustrade, he had collected a heap of stones on the platform itself. As soon as the blocks of stones on the parapet were spent, he turned to this latter heap. He stooped, rose, stooped and rose again with incredible agility. He would thrust his great gnomes head over the balustrade; then there dropped an enormous stonethen another and another. Now and then he followed a specially promising one with his eye, and when he saw that it killed its man, he grunted a hm! of satisfaction. -Nevertheless the beggars did not lose courage. Twenty times already had the massive door which they were so furiously storming shaken under the weight of their oaken battering-ram, multiplied by the strength of a hundred men. The panels cracked, the carvings flew in splinters, the hinges at each shock danced upon their hooks, the planks were displaced, the wood smashed to atoms ground between the sheathings of iron. Fortunately for Quasimodo there was more iron than wood. -He felt, however, that the great door was giving way. Although he could not hear it, every crash of the battering-ram shook him to his foundation, as it did the church. As he looked down upon the Vagabonds, full of exaltation and rage, shaking their fists at the gloomy and impassive fa?ade, he coveted for himself and the gipsy girl the wings of the owls flitting away in terror over his head. -His shower of stones was not sufficient to repulse the assailants. -At this desperate moment his eye fell on two long stone rain-gutters which discharged themselves immediately over the great doorway, a little below the balustrade from whence he had been crushing the Argotiers. The internal orifice of these gutters was in the floor of the platform. An idea occurred to him. He ran and fetched a fagot from the little chamber he occupied, laid over the fagot several bundles of laths and rolls of leadammunition he had not yet made use ofand after placing this pile in position in front of the orifice of the gutters, he set fire to it with his lantern. -During this time, as the stones no longer fell, the truands had ceased looking upward. The bandits, panting like a pack of hounds baying the wild boar in his lair, pressed tumultuously round the great door, disfigured now and injured by the great battering-ram, but still erect. They waited, eager and trembling, for the grand strokethe blow that should bring it crashing down. Each strove to get nearest to be the first, when it should open, to rush into that opulent Cathedral, that vast repository in which the riches of three centuries were heaped up. They reminded one another with roars of exultation and rapacity of the splendid silver crosses, the fine brocade copes, the silver-gilt tombs, of all the magnificence of the choir, the dazzling display on high festivals, the Christmas illuminations, the Easter monstrances glittering like the sun, and all the splendid solemnities in which shrines, candlesticks, pixes, tabernacles, and reliquaries crusted the altars with gold and diamonds. It is very certain that at this exciting moment every one of the truands was thinking much less about the deliverance of the gipsy girl than the plundering of Notre-Dame. Indeed, we can very well believe that to the majority of them Esmeralda was merely a pretextif plunderers have any call for pretexts. -Suddenly, at the moment when they were crowding round the battering-ram for a final effort, each one holding his breath and gathering up his muscles to give full force to the decisive blow, a howl more agonizing than that which succeeded the fall of the great beam arose from the midst of them. Those who were not screaming, those who were still alive, looked and saw two streams of molten lead pouring from the top of the edifice into the thickest of the crowd. The waves of that human sea had sunk under the boiling metal which, at the two points where it fell, had made two black and reeking hollows, like hot water poured on snow. There lay dying, wretches burned almost to a cinder and moaning in agony; and besides the two principal streams, drops of this hideous rain fell from scattered points on to the assailants, penetrating their skulls like fiery gimlets, pattering on them like red-hot hailstones. -The screams were heart-rending. Throwing down the battering-ram on the dead bodies, they fled in complete panic the boldest with the most timidand for a second time the Parvis was emptied. -Every eye was now directed upward to the top of the church. They beheld an extraordinary sight. On the topmost gallery, higher up than the great rose-window, a huge flame ascended between the two steeples, throwing out whirlwinds of sparks and shooting tongues of fire into the smoke as it was caught by the wind. Below this flame, under the balustrade whose carved trefoils showed black against the glare, two gargoyles vomited incessantly that burning shower, the silvery stream of which shone out upon the darkness of the lower part of the fa?ade. As they neared the ground the two streams of liquid lead spread out into a spray, like water from the rose of a monster watering-can. Above the flame, the huge towers, of each of which two sides sharply outlined one black, the other glowing redwere visible, seemed more enormous still by the immensity of the shadow they cast upon the sky. Their myriad sculptured devils and dragons assumed a sinister aspect. In the flickering radiance of the fire they appeared to movevampires grinned, gargoyles barked, salamanders blew the fire, griffins sneezed in the smoke. And among these monsters, thus awakened from their stony slumber by all this flame and uproar, there was one that walked about and passed from time to time before the blazing front of the pile, like a bat before a torch. -Assuredly this strange beacon-light must have awakened the lonely wood-cutter on the far Bictre hills, startled to see the gigantic shadows of the towers of Notre-Dame wavering on his coppices. -The silence of terror now fell upon the truands; and through it they heard the cries of alarm of the clergy shut up in their cloister like frightened horses in a burning stable, the stealthy sound of windows opened quickly and still more quickly shut again, the stir inside the surrounding houses and the H?tel-Dieu, the roar and crackle of the fire, the groans of the dying, and the continuous patter of the shower of boiling lead upon the pavement. -Meanwhile the chief Vagabonds had retired under the porch of the Gondelaurier mansion and were holding a council of war. The Duke of Egypt, seated on a post, was contemplating with religious awe the phantasmagoric pile blazing two hundred feet aloft in the air. Clopin Trouillefou gnawed his great fists with rage. -Impossible to make an entrance, he muttered between his teeth. -An enchanted church! growled the old Bohemian, Mathias Hungadi Spicali. -By the Popes whiskers! said a grizzled truand who had seen active service, but these two rain-pipes spit molten lead at you better than the loopholes of Lectoure. -Do you see that demon going to and from in front of the fire? cried the Duke of Egypt. -By God! exclaimed Clopin, tis that damned ringer; tis Quasimodo! -The Bohemian shook his head. I tell you tis the spirit Sabnac, the great marquis, the demon of fortifications. He has the form of an armed soldier and a lions head. Sometimes he is mounted on a grewsome horse. He turns men into stones and builds towers of them. He has command over near on fifty legions. Tis he, sure enough. I should know him anywhere. Sometimes he has on a fine robe wrought with gold, after the fashion of the Turks. -Where is Bellevigne de ltoile? asked Clopin. -Dead, answered a truand woman. -Notre-Dame is keeping the H?tel-Dieu busy, said Andry le Rouge with a vacant laugh. -Is there no way to force that door? cried the King of Tunis, stamping his foot. -The Duke of Egypt pointed with a mournful gesture to the two rivulets of boiling lead which continued to streak the dark front of the building. -Churches have been known to defend themselves thus, he observed with a sigh. Saint-Sophia in Constantinople, forty years ago, threw down the crescent of Mahomet three times running just by shaking her domes, which are her heads. William of Paris, who built this one, was a magician. -Are we then to slink away pitifully with our tails between our legs? cried Clopin. Leave our sister here for these cowled wolves to hang to-morrow? -And the sacristy where there are cart-loads of treasure! added a Vagabond, of whose name, to our great regret, we are ignorant. -By the beard of Mahomet! exclaimed Trouillefou. -Lets have another try, suggested the truand. -But Mathias Hungadi shook his head. We shall never get in by that door. We must find some joint in the enchanted armour. A hole, a postern door, a chink of some kind. -Whos with me? said Clopin. I am going back. By-the-bye, wheres the little scholar Jehan? -Hes dead, no doubt, answered some one, for one does not hear his laugh. -The King of Tunis frowned gloomily. - Tis a pity. There was a stout heart under that rattling armour. And Master Pierre Gringoire? -Captain Clopin, said Andry le Rouge, he made off before we got as far as the Pont-aux-Changeurs. -Clopin stamped his foot. Gueule-Dieu! tis he that thrust us into this business, and now he leaves us in the very thick of it. A prating poltroon! -Captain Clopin, announced Andry le Rouge, who had been looking down the Rue du Parvis, here comes the little scholar. -Praised be Pluto! said Clopin. But what the devil is he dragging after him? -It was, in truth, Jehan, coming along as quickly as his cumbrous paladin accoutrements would permit of, with a long ladder, which he tugged stoutly over the pavement, more breathless than an ant harnessed to a blade of grass twenty times her own length. -Victory! Te Deum! shouted the scholar. Heres the ladder from the Saint-Landry wharf. -Clopin went up to him. Little one, said he, what art thou going to do with that ladder, corne-Dieu? -Ive secured it, answered Jehan panting. I knew where it wasunder the shed of the lieutenants house. Theres a girl there whom I knowshe thinks me a very Cupido for beauty. It was through her I managed to get the ladder, and here I am, Pasque-Mahom! The poor soul came out in her smock to let me in. -Yes, yes, said Clopin, but what wilt thou do with this ladder? -Jehan gave him a sly, knowing look and snapped his fingers like castanets. He was sublime at this moment. He had on his head one of those overloaded helmets of the fifteenth century which struck terror to the heart of the foe by their monstrous-looking crests. Jehans bristled with ten iron beaks, so that he might have contended with the Homeric ship of Nestor for the epithet of dekemboloV. -What do I mean to do with it, august King of Tunis? Do you see that row of statues with the faces of imbeciles over there above the three arches of the doorway? -Yes; what of them? -That is the gallery of the King of France. -Well, whats that to us? said Clopin. -You shall see. At the end of that gallery there is a door that is closed with a latch; with this ladder I reach that door, and then Im in the church. -Let me go up first, child. -No, comrade, the ladders mine. Come onyou shall be second. -Beelzebub strangle thee! said Clopin sulkily. I will be second to nobody. -Then, Clopin, go fetch thyself a ladder. And Jehan set off running across the Place, dragging his ladder after him and shouting, Follow, boys! -In an instant the ladder was set up and placed against the balustrade of the lower gallery over one of the side doors. The crowd of beggars, shouting and hustling, pressed round the foot of it wanting to ascend; but Jehan maintained his right, and was the first to set foot on the steps of the ladder. The ascent was pretty long. The gallery of the kings is, at this day, about sixty feet from the ground; but at that period it was raised still higher by the eleven steps of the entrance. -Jehan ascended slowly, much encumbered by his heavy armour, one hand on the ladder, the other grasping his crossbow. When he was half-way up he cast a mournful glance over the poor dead Argotiers heaped on the steps. Alas! said he, here are corpses enough for the fifth canto of the Iliad! He continued his ascent, the Vagabonds following him, one on every step of the ladder. To see that line of mailed backs rising and undulating in the dark, one might have taken it for a serpent with steely scales rearing itself on end to attack the church, and the whistling of Jehan, who represented its head, completed the illusion. -The scholar at last reached the parapet of the gallery, and strode lightly over it amid the applause of the whole truandry. Finding himself thus master of the citadel, he uttered a joyful shoutand then stopped short, petrified. He had just caught sight, behind one of the royal statues, of Quasimodo crouching in the gloom, his eye glittering ominously. -Before another of the besiegers had time to gain a footing on the gallery, the redoubtable hunchback sprang to the head of the ladder, seized without a word the ends of the two uprights in his powerful hands, heaved them away from the wall, let the long and pliant ladder, packed with truands from top to bottom, sway for a moment amid a sudden outcry of fear, then suddenly, with superhuman force, flung back this living cluster into the Place. For an instant the stoutest heart quailed. The ladder thrust backward stood upright for a moment, swayed, then suddenly, describing a frightful arc of eighty feet in radius, crashed down upon the pavement with its living load more rapidly than a drawbridge when its chain gives way. There was one universal imprecation, then silence, and a few mutilated wretches were seen crawling out from among the heap of dead. -A murmur of mingled agony and resentment succeeded the besiegers first shouts of triumph. Quasimodo, leaning on his elbows on the balustrade, regarded them impassively. He might have been one of the old long-haired kings at his window. -Jehan Frollo found himself in a critical position. He was alone on the gallery with the redoubtable bell-ringer, separated from his companions by eighty feet of sheer wall. While Quasimodo was engaged with the ladder, the scholar had run to the postern which he expected to find on the latch. Foiled! The bell-ringer, as he entered the gallery, had locked it behind him. Thereupon Jehan had hidden himself behind one of the stone kings, not daring to breathe, but fixing upon the terrible hunchback a wide-eyed and bewildered gaze, like the man who courted the wife of a menagerie keeper, and going one evening to a rendezvous, scaled the wrong wall and found himself suddenly face to face with the polar bear. -For the first few moments the hunchback did not notice him; but presently he turned his head and straightened himself with a jerkhe had caught sight of the scholar. -Jehan prepared himself for a savage encounter, but his deaf antagonist did not move; only he kept his face turned towards him and regarded him steadily. -Ho! ho! said Jehan, why dost thou glare at me so with that single surly eye? And so saying, the young scamp began stealthily raising his cross-bow. Quasimodo! he cried, Im going to change thy nickname. Henceforth they shall call thee the blind bell-ringer. -He let fly the winged shaft; it whistled and drove into the hunchbacks left arm. Quasimodo was no more disturbed by it than the effigy of King Pharamond by the scratch of a penknife. He took hold of the arrow, drew it out of his arm, and calmly broke it across his powerful knee. Then he dropped rather than threw the two pieces to the ground. But he did not give Jehan time to discharge another shaft. The arrow broken, Quasimodo with a snort leapt like a locust upon the boy, whose armour was flattened by the shock against the wall. -And now, in the half darkness, by the flickering light of the torches, a horrible scene was enacted. -In his left hand Quasimodo grasped both Jehans arms, who made no struggle, so utterly did he give himself up for lost; then, with his right, the hunchback proceeded to take off one by one, and with sinister deliberation, the several pieces of the scholars iron shellsword, dagger, helmet, breastplate, armpieceslike a monkey peeling a walnut, and dropped them at his feet. -When Jehan found himself thus disarmed, divested of all shield and covering, naked and helpless in those formidable arms, he did not attempt to parley with his deaf enemy. Instead, he fell to laughing impudently in his face, and with all the careless assurance of a boy of sixteen, burst into a song at that time popular in the streets: -The town of Cambrai is finely clad, -But Marafin has stripped her. -He had not time to finish. Quasimodo was seen to mount the parapet of the gallery, holding the scholar by the feet in one hand only and swinging him over the abyss like a sling. Then came a sound like a box of bones dashing against a wall, and something came hurtling down that stopped halfway in its descent, caught by one of the projections of the building. It was a dead body bent double, the loins broken, the skull empty. -A cry of horror went up from the truands. -Revenge! yelled Clopin. Sack! sack! replied the multitude. To the assault! -An appalling uproar followed, in which every language, every patois, every conceivable accent was mingled. The death of the poor little scholar inspired the crowd with furious energy. They were torn with anger and shame at having been so long held in check by a miserable hunchback. Their rage found them ladders, multiplied their torches, and in a few minutes Quasimodo, to his consternation and despair, beheld the hideous swarm mounting from all sides to the assault of Notre-Dame. They who had no ladders had knotted ropes; they who had no ropes clambered up by the carvings, helping themselves up by one anothers rags. There was no means of forcing back this rising tide of frightful forms. Fury reddened the ferocious faces, sweat poured from the grimy foreheads, eyes glared viciously. It was as if some other church had sent out her gorgons, her dragons, her goblins, her demons, all her most fantastic sculptures to the assault of Notre-Damea coating of living monsters covering the stone monsters of the fa?ade. -Meanwhile a thousand torches had kindled in the Place. The wild scene, wrapped until now in dense obscurity, suddenly leapt out in a blaze of light. The Parvis was brilliantly illumined and cast a radiance on the sky, while the blazing pile on the high platform of the church still burned and lit up the city far around. The vast outline of the two towers, thrown far across the roofs of Paris, broke this brightness with a wide mass of shadow. The city appeared to be rousing itself from its slumbers. Distant tocsins uttered their warning plaints. The truands howled, panted, blasphemed, and climbed steadily higher, while Quasimodo, impotent against so many enemies, trembling for the gipsy girl as he saw those savage faces approaching nearer and nearer to his gallery, implored a miracle from heaven, and wrung his hands in despair. -Chapter 5 - The Closet where Monsieur Louis of France Recites his Orisons -The reader perhaps remembers that Quasimodo, a moment before catching sight of the nocturnal band of truands and scrutinizing Paris from the height of his steeple, saw but a single remaining light twinkling at a window in the topmost storey of a grim and lofty building beside the Porte Saint-Antoine. The building was the Bastille, the twinkling light was the taper of Louis XI. -The King had, in fact, been in Paris these two days past, and was to set out again the next day but one for his citadel of Montilz-les-Tours. He made but rare and short visits to his good city of Paris, not feeling himself sufficiently surrounded there by pitfalls, gibbets, and Scottish archers. -That day he had come to sleep at the Bastille. The great chamber, five toises square, which he had at the Louvre, with its splendid chimney-pieces bearing the effigies of twelve great beasts and thirteen great prophets, and his bed, eleven feet by twelve, were little to his taste. He felt lost amid all these grandeurs. The good homely King preferred the Bastille, with a chamber and bed of more modest proportions; besides, the Bastille was stronger than the Louvre. -This chambrette which the King reserved for his own use in the famous prison was spacious enough, nevertheless, and occupied the uppermost storey of a turret forming part of the donjon-keep. It was a circular apartment hung with matting of shining straw, the rafters of the ceiling being decorated with raised fleurs de lis in gilt metal interspaced with colour, and wainscotted with rich carvings sprinkled with metal rosettes and painted a beautiful vivid green made of a mixture of orpiment and fine indigo. -There was but one window, a long pointed one, latticed by iron bars and iron wire, and still further darkened with fine glass painted with the arms of the King and Queen, each pane of which had cost twenty-two sols. -There was also but one entrance, a door of the contemporary style under a flattened arch, furnished inside with a tapestry hanging, and outside with one of those porches of Irish wooddelicate structures of elaborately wrought cabinet-work which still abounded in old mansions a hundred and fifty years ago. Although they disfigure and encumber the places, says Sauval in desperation, our old people will not have them removed, but keep them in spite of everybody. -Not a single article of the ordinary furniture of a room was to be seen hereneither benches, nor trestles, nor forms; neither common box-stools, nor handsome ones supported by pillars and carved feet at four sols apiece. There was one folding arm-chair only, a very magnificent one, its frame painted with roses on a crimson ground, and the seat of crimson Cordova leather with a quantity of gold-headed nails. The solitary state of this chair testified to the fact that one person alone was entitled to be seated in the room. Beside the chair and close under the window was a table covered by a cloth wrought with figures of birds. On the table was a much-used inkstand, a few sheets of parchment, some pens, and a goblet of chased silver; farther off, a charcoal brasier and a prie-dieu covered with crimson velvet and ornamented with gold bosses. Finally, at the other end of the room, an unpretentious bed of red and yellow damask with no decoration of any sort but a plain fringe. This bed, famous as having borne the sleep or sleeplessness of Louis XI, was still in existence two hundred years ago in the house of a councillor of state, where it was seen by the aged Mme. Pilou, celebrated in Le Grand Cyrus under the name of Arricidie and of La Morale Vivante. -Such was the room known as the closet where Monsieur Louis of France recites his orisons. -At the moment at which we have introduced the reader into it, this closet was very dark. Curfew had rung an hour back, night had fallen, and there was but one flickering wax candle on the table to light five persons variously grouped about the room. -The first upon whom the light fell was a gentleman superbly attired in doublet and hose of scarlet slashed with silver and a cloak with puffed shoulder-pieces of cloth of gold figured with black, the whole gorgeous costume appearing to be shot with flames wherever the light played on it. The man who wore it had his heraldic device embroidered in vivid colours on his breasta chevron and a stag passant, the scutcheon supported by a branch of olive dexter and a stags horn sinister. In his girdle he wore a rich dagger, the silver-gilt hilt being wrought in the form of a helmet and surmounted by a counts coronet. He had a venomous eye, and his manner was haughty and overbearing. At the first glance you were struck by the arrogance of his face, at the second by its craftiness. He stood bareheaded, a long written scroll in his hand, behind the arm-chair in which sat a very shabbily dressed personage in an uncouth attitude, his shoulders stooping, his knees crossed, his elbow on the table. Picture to yourself in that rich Cordovan chair a pair of bent knees, two spindle shanks poorly clad in close-fitting black worsted breeches, the body wrapped in a loose coat of fustian the fur lining of which showed more leather than hair, and to crown the whole, a greasy old hat of mean black felt garnished all round by a string of little leaden figures. This, with the addition of a dirty skull-cap, beneath which hardly a hair was visible, was all that could be seen of the seated personage. His head was bowed so low on his breast that nothing was visible of his deeply shadowed face but the end of his nose, on which a ray of light fell, and which was evidently very long. By his emaciated and wrinkled hands one divined him to be an old man. It was Louis XI. -At some distance behind them, two men habited after the Flemish fashion were conversing in low tones. They were not so completely lost in the gloom but that any one who had attended the performance of Gringoires Mystery could recognise them as the two chief Flemish envoys: Guillaume Rym, the sagacious pensionary of Ghent, and Jacques Coppenole, the popular hosier. It will be remembered that these two men were concered with the secret politics of Louis XI. -And finally, quite in the dim background near the door, there stood, motionless as a statue, a brawny, thick-set man in military accoutrements and an emblazoned coat, whose square, low-browed face with its prominent eyes, immense slit of a mouth, ears concealed beneath two wide flaps of smooth hair, seemed a cross between the bulldog and the tiger. -All were uncovered except the King. -The knightly personage standing behind the King was reading out items from a sort of long memorandum, to which his Majesty appeared to listen attentively. The two Flemings whispered together. -By the rood! grumbled Coppenole, Im tired of standing. Is there never a chair here? -Rym replied with a negative gesture, accompanied by a discreet smile. -Croix-Dieu! resumed Coppenole, sorely exercised at having to lower his voice, I am devoured by the desire to plump myself down cross-legged on the floor as I do in my own shop. -You had best beware of doing so, Ma?tre Jacques, was the reply. -Heyday! Ma?tre Guillaume, may a man then be only on his feet here? -Or on his knees, said Rym. At that moment the King raised his voice and they ceased their talking. -Fifty sols for the gowns of our valets, and twelve livres for the mantles of the crown clerks! Thats the way! Pour out the gold by tons! Are you crazed, Olivier? -As he spoke the old man raised his head, and you could see the golden shells of the collar of Saint-Michael glittering round his neck. The candle shone full on his fleshless and morose countenance. He snatched the paper from the hands of the other. -You are ruining us! he cried, casting his hollow eyes over the schedule. Whats all this? What need have we of so prodigious a household? Two chaplains at ten livres a month each, and a chapel clerk at a hundred sols! A valetde-chambre at ninety livres a year! Four kitchen masters at a hundred and sixty livres a year each! A roaster, a soupdresser, a sauce-dresser, a head cook, an armourer, two sumpter men at the rate of ten livres a month each! Two turnspits at eight livres! A groom and his two helpers at four and twenty livres a month! A porter, a pastry-cook, a baker, two carters, each at sixty livres a year! And the marshal of forges a hundred and twenty livres! And the master of our exchequer chamber twelve hundred livres! And the comptroller five hundred livres! And God knows what besides! Its raving madness! The wages of our domestics are simply stripping France bare. All the treasure of the Louvre would melt away before such a blaze of expense! We shall have to sell our plate! And next year, if God and Our Lady (here he raised his hat) grant us life, we shall have to drink our tisanes from a pewter pot! -At which he glanced at the silver goblet sparkling on the table, coughed, and went on: -Master Olivier, princes who reign over great realms as kings and superiors should not allow sumptuousness to be engendered in their households, inasmuch as that is a fire which will spread from thence to the provinces. And so, Master Olivier, make no mistake about this. Our expenses increase with every year, and the thing displeases us. Why, pasque-Dieu! up till 79 it never exceeded thirty-six thousand livres. In 80 it rose to forty-three thousand six hundred and nineteen livres. I have the figures in my head. In 81 it was sixty-six thousand six hundred and eighty livres, and this year, faith of my body! it will come to eighty thousand livres. Doubled in four years! Monstrous! -He stopped to take breath, then resumed with vehemence: I see none about me but people fattening on my leanness. Ye suck my money from me at every pore! -All kept silence. It was one of those fits of anger that must be allowed to run their course. He continued his complaints. -It is the same thing with that Latin memorial from the great lords of France requesting us to re-establish what they call the great offices of the Crown. Offices! call them rather burdensburdens that crush us to the ground. Ah, messieurs! you tell us we are no King to reign dapifero nullo buticulario nullo!1 But we will let you see, pasque-Dieu! whether we are a King or no! -He smiled in the consciousness of his power, his ill-humour was allayed, and he turned to the Flemings: -Look you, Gossip Guillaume, the grand baker, the grand butler, the grand chamberlain, the seneschal are not worth the meanest valet. Bear this in mind, Gossip Coppenole, they are of no use whatever. Standing thus useless about the King, they put me in mind of the four evangelists that surround the face of the great clock of the palace, and that Philippe Brille has just renovated. They are gilded, but they do not mark the hour, and the clock hand could do excellently well without them. -He mused for a moment and added, shaking his old head: Ho! ho! by Our Lady, I am not Philippe Brille, and I will not regild the great vassals of the crown. Proceed, Olivier. -The person thus addressed received the schedule-book from his hands and went on reading aloud: -To Adam Tenon, assistant keeper of the seals of the provostry of Paris, for the silver, workmanship, and engraving of the said seals which have had to be renewed, inasmuch as the former ones, being old and worn out, could no longer be used, twelve livres parisis. -To Guillaume Frre, the sum of four livres four sols parisis for his wages and trouble in having fed and maintained the pigeons of the two pigeon-houses at the H?tel des Tournelles during the months of January, February, and March of this year, for the which he has furnished seven sestiers of barley. -To a Franciscan for shriving a criminal, four sols parisis. -The King listened in silence. From time to time he coughed, and then raised the goblet to his lips and drank a mouthful with a wry face. -In this year have been made, continued the reader, by order of the law, by sound of trumpet, through the streets of Paris, fifty-six public proclamations. Account not yet rendered. -For search made in divers places in Paris and elsewhere after treasure said to be conceled in the said places, but nothing has been found, forty-five livres parisis. -Burying a florin to dig up a sou, commented the King. -For putting in, at the H?tel des Tournelles, six panes of white glass, at the place where the iron cage stands, thirteen sols. For making and delivering on the day of the mustering of the troops, four escutcheons bearing the arms of our said lord, wreathed round with chaplets of roses, six livres. A pair of new sleeves to the Kings old doublet, twenty sols. A pot of grease to grease the Kings boots, fifteen deniers. A new sty for lodging the Kings black swine, thirty livres parisis. Several partitions, planks, and trap-doors, for the safe-keeping of the lions at the H?tel Saint-Paul, twenty-two livres. -Costly beasts, these, said Louis XI. But no matter, it is a magnificence befitting a King. There is a great tawny lion that I love for his engaging ways. Have you seen him, Ma?tre Guillaume? It is fitting that princes should keep these marvellous animals. For dogs, we kings should have lions; and for cats, tigers. The great beseems a crown. In the days of the pagan worshippers of Jupiter, when the people offered a hundred bullocks and a hundred sheep in the churches, the emperors gave a hundred lions and a hundred eagles. That was very fierce and noble. The kings of France have always had these roarings around their throne. Nevertheless, to do me justice, it must be admitted that I spend less in that way than my predecessors, and that I am less ostentatious in the matter of lions, bears, elephants, and leopards.Continue, Ma?tre Olivier. This was for the benefit of our friends, the Flemings. -Guillaume Rym bowed low, while Coppenole, with his surly face, looked much like one of the bears of whom his Majesty had spoken. The King paid no attention; he had just taken a sip from the goblet, and was spitting out the beverage again with a Faugh! the nasty stuff! -The reader went on: For the food of a rogue and vagabond kept locked up for the last six months in the cell at the Skinners yard until it should be known what was to be done with him, six livres four sols. -Whats that? interrupted the King. Feeding what ought to be hanged! Pasque-Dieu! Ill not give another sol for that food. Olivier, arrange this matter with M. dEstouteville, and see to it that this very night preparations are made to unite this gallant with the gallows. Go on. -Olivier made a mark with his thumb-nail against the item rogue and vagabond, and proceeded: -To Henriet Cousin, chief executioner at the Justice of Paris, the sum of sixty sols parisis, to him adjudged and accorded by the Lord Provost of Paris for having purchased, by order of the said Lord Provost, a great broad-bladed sword, to be used for executing and decapitating the persons condemned by law for their delinquencies, and having it furnished with a scabbard and all necessary appurtenances; and similarly for the repair and putting in order of the old sword, which had been splintered and notched in executing justice on Messire Louis of Luxembourg, as can be plainly shown. -The King broke in: Enough! I give order for that sum with all my heart. These are expenses I do not look at twice. I have never regretted that money. Proceed. -For constructing a new cage -Ah! said the King, grasping the arms of his chair, I knew I had come to the Bastille for something special. Stop, Master Olivier, I will see that cage myself. You shall read over the cost of it to me while I examine it. Messieurs the Flemings, you must come and see this; it is curious. -He rose to his feet, leaned on the arm of his interlocutor, signed to the sort of mute standing beside the door to precede them, to the two Flemings to follow, and left the chamber. -The Kings cortge was recruited at the door by a party of men-at-arms ponderous with steel, and slim pages carrying torches. It proceeded for some time through the interior of the grim donjon-keep, perforated by flights of stairs and corridors even to the thickness of the walls. The captain of the Bastille walked at its head, and directed the opening of the successive narrow doors before the bent and decrepit King, who coughed as he walked along. -At each door every head was obliged to stoop, except that of the old man already bent with age. Hum! said he between his gums, for he had no teeth; we are in excellent trim for the gate of the sepulchre. A low door needs a stooping passenger. -At length, after passing through the last door of all, so encumbered with complicated locks that it took a quarter of an hour to get them all open, they entered a lofty and spacious Gothic hall, in the centre of which they could discern by the light of the torches a great square mass of masonry, iron, and wood-work. The interior was hollow. It was one of those famous cages for state prisoners familiarly known as Fillettes du roi little daughters of the King. There were two or three small windows in its walls, but so closely grated with massive iron bars that no glass was visible. The door consisted of a huge single slab of stone, like that of a tomb one of those doors that serve for entrance alone. Only here, the dead was alive. -The King began pacing slowly. round this small edifice, examining it with care, while Ma?tre Olivier, who followed him, read aloud the items of the account: -For making a great wooden cage of heavy beams, joists, and rafters, measuring nine feet in length and eight in breadth, and seven feet high between roof and floor, mortised and bolted with great iron bolts; which has been placed in a certain chamber situated in one of the towers of the Bastille Saint-Antoine; in the which said cage is put and kept by command of our lord the King a prisoner, who before inhabited an old, decayed, and unserviceable cage. Used in the building of the said new cage, ninety-six horizontal beams and fifty-two perpendicular, ten joists, each three toises long. Employed in squaring, planing, and fitting the same wood-work in the yard of the Bastille, nineteen carpenters for twenty days -Fine solid timber, that! remarked the King, rapping his knuckles on the wood. -Used in this cage, continued the other, two hundred and twenty great iron bolts nine feet and eight feet long, the rest of medium length, together with the plates and nuts for fastening the said bolts; the said iron weighing in all three thousand seven hundred and thirty-five pounds; besides eight heavy iron clamps for fixing the said cage in its place, altogether two hundred and eighteen pounds; without reckoning the iron of the grating to the windows of the chamber and other items -Heres a deal of iron to restrain the levity of a spirit! -The whole amounts to three hundred and seventeen livres, five sols, seven deniers. -Pasque-Dieu! exclaimed the King. This oath, which was the favourite one of Louis XI, apparently aroused some one inside the cage: there was sound of clanking chains being dragged across its floor, and a feeble voice that seemed to issue from the tomb, wailed: Sire! Sire, mercy! The speaker was not visible. -Three hundred and seventeen livres, five sols, seven deniers! repeated Louis XI. -The voice of lamentation which had issued from the cage chilled the blood of all present, even Ma?tre Oliver. The King alone gave no evidence of having heard it. At his command Olivier resumed his reading, and his Majesty coolly continued his inspection of the cage. -Besides the above, there has been paid to a mason, for making the holes to fix the window-grating and the flooring of the chamber containing the cage, forasmuch as the floor would not otherwise have supported the said cage by reason of its weighttwenty-seven livres, fourteen sols parisis -The voice began its wailing again. Mercy, Sire! I swear to you it was Monsieur the Cardinal of Angers who committed the treasonnot I! -The masons charge is exorbitant! said the King. Go on, Olivier. -Olivier went on: To a joiner for window-frames, bed-stead, closet-stool, and other thingstwenty livres, two sols parisis -The voice also went on: Woe is me, Sire! will you not hear me? I protest that it was not I who wrote that to the Duke of Guyenne, but Monsieur the Cardinal Balue! -The joiner is dear, observed the King. Is that all? -No, Sire. To a glazier for the windows of the said chamber, forty-six sols, eight deniers parisis. -Have mercy, Sire! cried the voice again. Is it not enough that all my possessions have been given to my judges my table service to M. de Torcy, my library to Ma?tre Pierre Doriolle, my tapestries to the Governor of Roussillon? I am innocent. Lo, these fourteen years have I shivered in an iron cage. Have mercy, Sire! and you shall find it in heaven! -Ma?tre Olivier, said the King, the total? -Three hundred and sixty-seven livres, eight sols, three deniers parisis -Notre-Dame! cried the King. Tis an outrageous cage! -He snatched the paper from Oliviers hand, and began to reckon it up himself on his fingers, examining the schedule and the cage by turnswhile the prisoner was heard sobbing within it. It was a dismal scene in the darkness, and the bystanders paled as they looked at one another. -Fourteen years, Sire! It is fourteen yearssince April, 1469. I conjure you in the name of the Holy Mother of God, listen to me, Sire! During all those years you have enjoyed the warmth of the sun; shall I, feeble wretch that I am, never see the light of day again? Mercy, Sire! Show mercy! Clemency is a noble virtue in a King, and turns aside the current of the wrath to come. Think you, your Majesty, that at the hour of death it will be a great satisfaction to a King to know that he has never let an offence go unpunished? Moreover, I never betrayed your Majestyit was Monsieur of Angers. And I have a very heavy chain on my foot with a huge iron ball attached to itfar heavier than there is any need for. Oh, Sire, have pity on me! -Olivier, said the King, shaking his head, I observe that they charge me the bushel of plaster at twenty sols, though it is only worth twelve. You will draw up this memorandum afresh. -He turned his back on the cage, and began to move towards the door of the chamber. The wretched prisoner judged by the withdrawal of the torchlight and by the sounds that the King was preparing to depart. -Sire! Sire! he cried in anguish. -The door closed. He saw nothing more, and heard nothing but the raucous voice of the turnkey singing close by: -Ma?tre Jean Balue -Has lost from view -His bishoprics all. -Monsieur de Verdun -Has now not got one; -Theyre gone, one and all. -The King returned in silence to his closet, followed by his train, all horror-struck at the last bitter cry of the prisoner. Suddenly his Majesty turned to the Governor of the Bastille. -By-the-bye, said he, was there not some one in that cage? -Pardieu! yes, Sire! answered the governor, dumfounded by the question. -And who? -Monsieur the Bishop of Verdun. -The King knew that better than any one, but it was a way he had. -Ah, said he blandly, with the air of remembering it for the first time, Guillaume de Harancourt, the friend of Monsieur the Cardinal Balue. A good fellow of a bishop! -A few minutes later, the door of the closet had opened and closed again on the five persons whom the reader found there at the beginning of this chapter, and who had severally resumed their places, their attitudes, and their whispered conversation. -During the Kings absence some despatches had been laid upon the table, of which he himself broke the seal. He then began reading them attentively one after another, motioned to Ma?tre Olivier, who seemed to fill the post of minister to him, to take a pen, and without imparting to him the contents of the despatches, began in a low voice to dictate to him the answers, which the latter wrote kneeling uncomfortably at the table. -Guillaume Rym watched them. -The King spoke so low that the Flemings could hear nothing of what he was dictating, except here and there a few isolated and scarcely intelligible fragments, such as: Maintain the fertile tracts by commerce and the sterile ones by manufactures.Show my lords the English our four bombards: the Londres, the Brabant, the Bourg-en-Bresse, the Saint-Omer.It is owing to artillery that war is now more reasonably carried on.To Monsieur de Bressuire, our friend.Armies cannot be maintained without contributions, etc. -Once he raised his voice. Pasque-Dieu! Monsieur the King of Sicily seals his letters with yellow wax like a King of France! Perhaps we do wrong to permit this. My good cousin of Burgundy accorded no arms of a field gules. The greatness of a house is secured by upholding the integrity of its prerogatives. Note that down, friend Olivier. -Another time: Oh, oh! said he, a big missive! What does our friend the Emperor demand of us now? Then, running his eye over the despatch and interrupting the perusal now and again with brief interjections: Certes, Germany is getting so grand and mighty it is scarcely credible. But we do not forget the old proverb: The finest country is Flanders; the finest duchy, Milan; the finest kingdom, France. Is that not so, Messieurs the Flemings? -This time Coppenole bowed as well as Guillaume Rym. The hosiers patriotism was tickled. -The last of the batch made Louis XI knit his brows. What have we here? he exclaimed. Complaints and petitions against our garrisons in Picardy! Olivier, write with all speed to Monsieur the Marshal de Rouault: That discipline is relaxed; that the men-at-arms, the nobles, the free archers, and the Swiss are doing infinite mischief to the inhabitants; that the military, not content with the good things they find in the dwellings of the husbandmen, must needs compel them with heavy blows of staves or bills to fetch them from the town wine, fish, spices, and other superfluous articles; that the King knows all this; that we mean to protect our people from annoyance, theft, and pillage; that such is our will, by Our Lady! That, furthermore, it does not please us that any musician, barber, or man-at-arms whatsoever, should go clad like a prince in velvet, silk, and gold rings; that such vanities are hateful to God; that we, who are a gentleman, content ourselves with a doublet of cloth at sixteen sols parisis the ell; that messieurs the varlets may very well come down to that price likewise. Convey and command thisTo M. de Rouault, our friend.Good. -He dictated this letter in a loud voice with a firm tone, and in short, abrupt sentences. As he spoke the last word, the door flew open and admitted a fresh person, who rushed into the chamber in breathless agitation, crying: -Sire! Sire! there is a rising of the populace of Paris! -The Kings grave face contracted, but such emotion as he displayed passed like a flash. He controlled himself. Compre Jacques, he said in a tone and with a look of quiet severity, you enter very abruptly. -Sire! Sire! there is a revolt! gasped Ma?tre Jacques. -Louis, who had risen from his seat, seized him roughly by the arm, and in a tone of concentrated anger and a side-long glance at the Flemings, said in his ear so as to be heard by him alone: Hold thy peace, or speak low! -The newcomer grasped the situation and proceeded to tell his news in a terrified whisper, the King listening unmoved, while Guillaume Rym directed Coppenoles attention to the messengers face and dress, his furred hood (caputia forrata), his short cloak (epitogia curta), his gown of black velvet, which proclaimed him a president of the Court of Accompts. -Scarcely had this person given the King a few details, when Louis exclaimed in a burst of laughter: Nay, in good sooth, speak up, Compre Coictier. What need to whisper thus? Our Lady knows we have no secrets from our good Flemish friends. -But, Sire -Speak up! said the King. -Compre Coictier stood in mute surprise. -So, resumed the Kingspeak out, monsieur. So there is a rising of the populace in our good city of Paris? -Yes, Sire. -Which is directed, you tell me, against Monsieur the Provost of the Palais de Justice? -It would seem so, replied the man, who still found his words with difficulty, so confounded was he by the sudden and inexplicable change in the Kings manner. -Where did the watch encounter the mob? asked Louis. -Advancing from the Great Truanderie towards the Pontaux-Changeurs. I met it myself on my way here in obedience to your Majestys orders. I heard some of them cry, Down with the Provost of the Palais! -And what is their grievance against the provost? -Oh, said Jacques, that he is their liege lord. -In truth? -Yes, Sire. They are rascals from the Court of Miracles. They have long been complaining of the provost whose vassals they are. They will not acknowledge him either as justiciary or as lord of the highway. -So, so! retorted the King, with a smile of satisfaction which he strove in vain to conceal. -In all their petitions to the Parliament, continued Compre Jacques, they claim to have only two mastersyour Majesty and their God; who is, I believe, the devil. -H, h! chuckled the King, rubbing his hands with that internal laugh which irradiates the countenance. He could not disguise his delight, though he made a momentary effort to compose himself. No one had the least idea what it meant, not even Olivier. He remained silent for a moment, but with a thoughtful and satisfied air. -Are they in force? he asked suddenly. -They are indeed, Sire, replied Coictier. -How many? -Six thousand at the very least. -The King could not repress a pleased Good!Are they armed? he went on. -With scythes, pikes, hackbuts, pickaxesevery description of violent weapon. -The King seemed in nowise disturbed by this alarming list. Compre Jacques thought it advisable to add: If your Majesty sends not speedy succour to the provest, he is lost! -We will send, said the King with simulated earnestness. Good! we will certainly send. Monsieur the Provost is our friend. Six thousand! These are determined rogues! Their boldness is extraordinary, and we are highly incensed thereat. But we have few men about us to-night. It will be time enough to-morrow morning. -Coictier gave a cry. This moment, Sire! They would have time to sack the court-house twenty times over, storm the manor, and hang the provost himself. For Gods sake, Sire, send before to-morrow morning! -The King looked him full in the face. I said to-morrow morning. It was one of those looks to which there is no reply. -After a pause, Louis again raised his voice. My good Jacques, you should know thatWhat did he corrected himselfwhat does the feudal jurisdiction of the provost comprise? -Sire, the Rue de la Calandre as far as the Rue de lHerberie, the Place Saint-Michel and places commonly called Les Mureaux situated near the Church of Notre-Dame des Champs, here the King lifted the brim of his hatwhich mansions are thirteen in number; further the Court of Miracles, further the Lazaretto called the Banlieue, further the whole of the high-road beginning at the Lazaretto and ending at the Porte Saint-Jacques. Of these several places he is reeve of the ways, chief, mean, and inferior justiciary, full and absolute lord. -So, ho! said the King, scratching his left ear with his right hand, that comprises a good slice of my town! Ah, Monsieur the Provost was king of all this! -This time he did not correct himself. He continued cogitating and as if talking to himself: Softly, Monsieur the Provost, you had a very pretty piece of our Paris! -Suddenly he burst out: Pasque-Dieu! what are all these people that claim to be highway-reeves, justiciaries, lords and masters along with us! that have their toll-gates at the corner of every field, their gibbet and their executioner at every cross-way among our people, so that, as the Greek thought he had as many gods as he had springs of water, the Persian as many as the stars he saw, the Frenchman reckons as many kings as he sees gibbets. Pardieu! this thing is evil, and the confusion of it incenses me! I would know if it be Gods pleasure that there should be in Paris any keepers of the highways but the King, any justiciary but our Parliament, any emperor but ourself in this empire? By my soul, but the day must come when there shall be in France but one king, one lord, one judge, one headsman, just as in paradise there is but one God! -He lifted his cap again and went on, still deep in his own thoughts, with the look and tone of a huntsman uncoupling and cheering on his pack: -Good, my people! Well done! Pull down these false lords! Do your work! At them! At them! Pillage, hang, sack them! Ah, you would be kings, my lords! At them! my people, at them! -He stopped himself abruptly, bit his lips as if to regain possession of his escaping thoughts, bent his piercing eye in turn on each of the five persons around him, and suddenly taking his hat in both hands and regarding it steadfastly, he exclaimed: Oh, I would burn thee, didst thou know what I have in my head! -Then casting around him the alert and suspicious glance of a fox stealing back to his holeNo matter, he said, we will send help to Monsieur the Provost. Most unfortunately we have very few troops here at this moment to send against such a mob. We must wait till to-morrow. Order shall then be restored in the city, and all who are taken shall be promptly hanged. -That reminds me, Sire, said Coictier, I forgot in my first perturbation, the watch have seized two stragglers of the band. If your Majesty pleases to see these men, they are here. -If it be my pleasure! cried the King. What! Pasque-Dieu! canst thou forget such a thing? Run quick. Olivier, do thou go and bring them here. -Ma?tre Olivier went out and returned immediately with the two prisoners, surrounded by archers of the body-guard. The first of the two had a wild, imbecile face, drunken and wonder-struck. He was clad in rags and walked with one knee bent and dragging his foot. The other presented a pale and smiling countenance, with which the reader is already acquainted. -The King scrutinized them a moment without speaking, then abruptly addressed the first prisoner: -What is thy name? -Gieffroy Pincebourde. -Thy trade? -Truand. -What wast thou doing in that damnable riot? -The truand gazed at the King, swinging his arms the while with an air of sottish stupidity. His was one of those uncouth heads in which the intellect is about as much at its ease as a light under an extinguisher. -Were you not going to outrageously attack and plunder your lord the Provost of the Palais? -I know they were going to take something from somebody, but thats all. -A soldier showed the King a pruning-hook which had been found on the truand. -Dost thou recognise this weapon? demanded the King. -Yes, tis my pruning-hook. I am a vine-dresser. -And dost thou know this man for thy companion? added Louis, pointing to the other prisoner. -No, I do not know him. -That will do, said the King; and motioning to the silent figure standing impassively at the door, whom we have already pointed out to the reader: Compre Tristan, he said, heres a man for you. -Tristan lHermite bowed, then whispered an order to a couple of archers, who carried off the unlucky truand. -Meanwhile the King had addressed himself to the other prisoner, who was perspiring profusely: Thy name? -Pierre Gringoire, Sire. -Thy trade? -Philosopher, Sire. -How comes it, rascal, that thou hast the presumption to go and beset our friend Monsieur the Provost of the Palais, and what hast thou to say with regard to this rising of the populace? -Sire, I was not in it. -Go to, ribald; wast thou not taken by the watch in that bad company? -No, Sire, there is a misapprehension; tis an unlucky mischance. I am a maker of tragedies, Sire. I beseech your Majesty to hear me. I am a poet. It is the craze of men of my profession to go about the streets at night. It was passing by, this evening; twas a mere chance. They took me without reason. I am innocent of this civil disturbance. Your Majesty sees that the truand did not know me. I conjure your Majesty -Hold thy tongue! said the King, between two sips of his tisane; thou wilt split our head. -Tristan lHermite approached, and pointing to Gringoire: Sire, shall we hang this one at the same time? -It was the first word he had spoken. Bah! returned the King carelessly, I see no objection. -But I doa great many, said Gringoire. -Our philosophers countenance at this moment rivalled the hue of the olive. He saw by the cold and indifferent air of the King that he had no resource but in something, excessively pathetic. He therefore threw himself at the feet of Louis XI, and, with gestures of despair, cried: -Sire, will your Majesty deign to listen to me? Sire, break not forth in thunders against so poor a thing as I the bolts of God strike not the lowly lettuce. Sire, you are an august and mighty monarch; have pity on a poor honest man who would be more incapable of inflaming a revolt than an icicle of producing a spark. Most gracious Sire, magnanimity is the virtue of the lion and of the King. Alas! severity does but exasperate the spirit; the fierce blast of the north wind will not make the traveller lay aside his mantle, but the suns gentle rays, warming him little by little, cause him at last to strip himself gladly to his shirt. Sire, you are the sun. I protest to you, my sovereign lord and master, that I am no disorderly companion of truands and thieves. Revolt and brigandage go not in the train of Apollo. I am no man to throw myself headlong into those clouds that burst in thunders of sedition. I am a faithful vassal of your Majesty. The same jealousy which the husband has for his wifes honour, the affection with which the son should requite his fathers love, a good vassal should feel for the glory of his King, should wear himself out for the upholding of his house, for the furtherance of his service. All other passions that might possess him were mere frenzy. These, Sire, are my maxims of state. Therefore judge me not as sedition-monger and pillager because my coat is out at elbows. Show me mercy, Sire, and I will wear out my knees in praying God for you day and night. Alas! I am not extremely rich, it is truerather, I am somewhat poor; but for all that, I am not vicious. It is not my fault. Every one knows that great wealth is not to be acquired from belles-lettres, and that the most accomplished writers have not always a great fire to warm them in winter. The advocates alone take all the grain, and leave nothing but the chaff for the other learned professions. There are forty very excellent proverbs upon the philosophers threadbare coat. Oh, Sire, clemency is the only light that can illumine the interior of a great soul. Clemency bears the torch before all the other virtues. Without her they are blind, groping for God in the darkness. Mercy, which is the same as clemency, produces loving subjectsthe most powerful body-guard that can surround a prince. What can it signify to your Majesty, by whom all faces are dazzled, that there should be one more poor man upon eartha poor, innocent philosopher crawling about in the slough of calamity, his empty purse flapping upon his empty stomach? Besides, Sire, I am a man of letters. Great kings add a jewel to their crown by patronizing learning. Hercules did not disdain the title of Musagetesleader of the Muses. Mathias Corvinus showed favour to Jean de Monroyal, the ornament of mathematics. Now tis an ill way of patronizing letters to hang the lettered. What a stain on Alexander had he hanged Aristotle! The act would not have been a beauty-spot upon the cheek of his reputation to embellish it, but a virulent ulcer disfiguring it. Sire, I wrote a very appropriate epithalamium for Mademoiselle of Flanders and Monsieur the most august Dauphin. That was not like a fire-brand of rebellion. Your Majesty can see that I am no dunce; that I have studied excellently, and that I have much natural eloquence. Grant me mercy, Sire! By so doing, you will perform an action agreeable to Our Lady, and I do assure you, Sire, that I am greatly frightened at the thought of being hanged! -So saying, the desperate Gringoire kissed the Kings shoe, whereat Guillaume Rym murmured low to Coppenole: He does well to crawl upon the floor. Kings are like the Cretan Jupiterthey have ears on their feet only. And Coppenole, unmoved by the peculiar attributes of the Cretan Jupiter, answered with a slow smile and his eye fixed on Gringoire: Ah, thats good! I could fancy I hear the Chancellor Hugonet begging mercy of me! -When Gringoire stopped at length, out of breath, he raised his head tremulously to the King, who was engaged in scratching off a spot on his breeches knee with his finger-nail, after which his Majesty took another mouthful from the goblet. But he said never a word, and this silence kept Gringoire on the rack. At last the King looked at him. -Heres a terrible babbler! said he. Then turning to Tristan lHermite: Bah! let him go! -Gringoire, giddy with joy, suddenly sat flat on the floor. -Free? growled Tristan. Your Majesty will not even have him caged for a while? -Compre, returned Louis XI, dost thou think it is for birds like this we have cages made at three hundred and seventy-seven livres, eight sols, three deniers apiece? Set him at liberty, the rascal, and send him off with a drubbing. -Ouf! cried Gringoire; here indeed is a great King! -And, fearing a counter-order, he hurried to the door, which Tristan opened for him with a very bad grace. The soldiers went out with him, driving him before them with great blows of their fists, which Gringoire bore like a true Stoic. -The good humour of the King, since the revolt against the provost had been announced to him, manifested itself at every point, and this unusual clemency was no insignificant sign of it. Tristan lHermite in his corner looked as surly as a dog that has seen much but got nothing. -Meanwhile the King was gaily drumming the Pont Audemer march on the arms of his chair. He was a dissembling prince, but he was much better able to conceal his sorrow than his joys. These outward and visible signs of rejoicing at good news sometimes carried him great lengthsthus, at the death of Charles the Bold, to vowing balustrades of silver to Saint-Martin of Tours; on his accession to the throne, of forgetting to give orders for his fathers obsequies. -Hah, Sire! suddenly exclaimed Jacques Coictier, what of the sharp attack of illness for which your Majesty sent for me? -Oh, said the King, truly I suffer greatly, Gossip Jacques. I have singings in the ear, and teeth of fire that rake my chest. -Coictier took the Kings hand and felt his pulse with a professional air. -Look at him now, Coppenole, said Rym in a low voice. There he is between Coictier and Tristan. That is his whole courta physician for himself, a hangman for the others. -As he felt the Kings pulse, Coictier assumed a look of great alarm. Louis regarded him with some anxiety, while the physicians countenance waxed gloomier every instant. The good man had no other means of subsistence but the Kings bad health; he accordingly made the most of it. -Oh, oh! he muttered at last, this is indeed serious. -Yes, is it not? said the King anxiously. -Pulsus creber, anhelans, crepitans, irregularis, 2 continued the physician. -Pasque-Dieu! exclaimed his Majesty. -This might carry off a man in less than three days. -Notre-Dame! cried the King. And the remedy, Gossip? -I am thinking of one, Sire. -He made Louis put out his tongue; then shook his head, pulled a long face, and in the midst of these anticsPardieu! Sire, he remarked suddenly, I must inform you that there is a receivership of episcopal revenues vacant, and that I have a nephew. -I give the receivership to thy nephew, Gossip Jacques; but take this fire from my breast. -Since your Majesty is so gracious, the physician went on, you will not refuse to assist me a little towards the building of my house in the Rue Saint-Andry des Arcs? -Hm! said the King. -I am at the end of my money, continued the doctor, and it would indeed be a pity that the house should be left without a roofnot for the sake of the house itself, which is plain and homely, but for the paintings of Jehan Fourbault which adorn the wainscotting. There is a Diana among them, flying in the air; but so excellently limned, so tender, so delicate, the attitude so artless, the hair so admirably arranged and crowned by a crescent, the flesh so white, that she leads those into temptation who regard her too closely. Then there is also another, a Ceresanother most admirable divinity seated on sheaves of corn, and crowned with a garland of wheat-ears intertwined with salsify and other flowers. Never were more amorous eyes, or shapelier limbs, or a nobler mien, or more graceful folds of drapery. It is one of the most innocent and perfect beauties that ever brush produced. -Tormentor! growled Louis, to what does all this tend? -I require a roof over these paintings, Sire, and, although it be but a trifle, I have no money left. -What will it cost, this roof of thine? -Oh, well; a roof of copper-gilt and with mythological figures, two thousand livres at most. -Ha! the assassin! screamed the King. He never draws me a tooth but he makes a diamond out of it! -Am I to have my roof? said Coictier. -Yes!and go to the devil; but cure me first. -Jacques Coictier made a profound obeisance and said: Sire, it is a repellent that will save you. We shall apply to your loins the great deterrent composed of cerade, clay of Armenia, white of egg, oil, and vinegar. You will continue the tisane, and we will answer for your Majestys safety. -A lighted candle never attracts one gnat only. Master Olivier, seeing the King in so liberal a mood, and judging the moment propitious, approached in his turn. -Sire -What do you want now? asked Louis. -Sire, your Majesty is aware that Simon Radin is dead. -Well? -He was Kings Councillor to the Court of Treasury. -Well? -Sire, his post is vacant. -As he spoke, Ma?tre Oliviers overbearing countenance changed its arrogance for cringingthe only alternation on the face of a courtier. The King looked him very straight in the face and answered dryly, I understand. -Ma?tre Olivier, he went on, the Marshal de Boucicaut says: There is no good gift but from the King; there is no good fishing but in the sea. I see you share Monsieur de Boucicauts opinion. Now harken to thiswe have a good memory. In 68 we made you a groom of the chamber; in 69, warder of the fort on the bridge of Saint-Cloud, with a salary of a hundred livres tournois (you wanted it parisis). In November, 73, by letters patent given at Gergeole, we appointed you ranger of the forest of Vincennes in place of Gilbert Acle, squire; in 75, warden of the forest of Rouvray-lez-Saint-Cloud, in place of Jacques le Maire; in 78, we graciously settled upon you, by letters patent sealed with a double seal of green wax, an annuity of ten livres parisis, for yourself and your spouse, chargeable on the Place aux Marchands, near the School of Saint-Germain; in 79, we made you warden of the forest of Senard, in the place of poor Jehan Diaz; then captain of the Castle of Loches; then Governor of Saint-Quentin; then captain of the Bridge of Meulan, of which you had yourself called count. Of the five sols fine paid by every barber who shaves on a holiday, you get threeand we get what you leave. We were pleased to change your surname of Le Mauvais as being too expressive of your mien. In 74, we granted you, to the great umbrage of our nobility, armorial bearings of many colours, which enables you to display a peacock breast. Pasque-Dieu! are you not surfeited? Is not the draught of fishes abundant and miraculous enough? Are you not afraid that one salmon more will sink your boat? Pride will be your ruin, my Gossip. Ruin and shame tread ever close upon the heels of pride. Remember that, and keep still. -These words, pronounced with severity, brought back the insolence to Oliviers face. -Good! he muttered almost aloud; tis evident the King is sick to-day, for he gives all to the physician. -Far from taking offence at this piece of effrontery, Louis resumed in a milder tone: Stay, I had forgotten too that I made you my ambassador at Ghent to Mme. Marie. Yes, gentlemen, he added, addressing himself to the Flemings, this man has been an ambassador. There, there, Gossip, turning to Olivier, let us not fall outwe are old friends. It is getting late. We have finished our businessshave me. -The reader has doubtless already recognised in Ma?tre Olivier the terrible Figaro whose part Providencethat master playwrightwove so skilfully into the long and sanguinary drama of Louis XI. We shall not attempt here to describe that baleful character. This barber to the King had three names. At Court they addressed him politely as Olivier le Daim; among the people he was Olivier le Diable. His real name was Olivier le Mauvaisthe Miscreant. -Olivier le Mauvais stood unmoved, sulking at the King, scowling at Jacques Coictier. -Yes, yes! the physician! he muttered between his teeth. -Quite so; the physician! repeated Louis with unwonted affability; the physician has yet more influence than thyself. The reason is not far to seekhe has hold over our entire body; thou only of our chin. Come, come, my poor barber, all will be well. Now, Gossip, perform thy office, and shave me; go fetch what is needful. -Olivier, seeing that the King was determined to take the matter as a jest, and that it was useless even to try to provoke him, went out grumbling to execute his orders. -The King rose and went to the window. Suddenly he threw it open with extraordinary excitement: -Oh, yes! he exclaimed, clapping his hands, theres a glare in the sky over the city. It is the Provost of the Palais burning; it can be nothing else. Ha! my good people, so ye aid me at last in the overthrow of the feudal lords! Gentlemen, and he turned to the Flemings, come and look at this. Is that not the red glare of a conflagration? -The two Flemings approached. -A great fire, said Guillaume Rym. -Oh! added Coppenole, his face lighting up suddenly, -that reminds me of the burning of the Seigneur dHymber-courts house. There must be a big revolt over there. -Think you so, Ma?tre Coppenole? and Louiss face beamed even brighter than the hosiers. Do you not think it will be difficult to check? -Croix-Dieu! Sire, it may cost your Majesty many a company of soldiers! -Ahcost methats different, rejoined the King. If I choose -If this revolt be what I suppose, continued the hosier boldly, you will have no choice in the matter, Sire. -My friend, said Louis XI, two companies of my bodyguard, and the discharge of a serpentine, are amply sufficient to put a mob of common people to the rout. -Regardless of the signs Guillaume Rym was making to him, the hosier seemed bent upon contesting the matter with the King. Sire, said he, the Swiss were common people too. Monsieur the Duke of Burgundy was a great seigneur, and held the canaille of no account. At the battle of Granson, Sire, he shouted: Cannoneers, fire upon these churls! and he swore by Saint-George. But the syndic Scharnachtal rushed upon the fine duke with his clubs and his men, and at the shock of the peasants with their bull-hides, the glittering Burgundian army was shattered like a pane of glass by a stone. There was many a knight killed there by the base-born churls, and Monsieur de Chateau-Guyon, the greatest lord in Burgundy, was found dead, with his great gray charger, in a little boggy field. -Friend, returned the King, you are speaking of a battle. This is but a riot, and I can put an end to it the moment I choose to lift a finger. -To which the other replied unconcernedly, That may be, Sire; but in that case, the hour of the people has not yet come. -Guillaume Rym thought it time to interfere. Ma?tre Coppenole, you are talking to a great King. -I know it, answered the hosier gravely. -Let him speak his mind, friend Rym, said the King. I like this plain speaking. My father, Charles VII, used to say that truth was sick. For my part, I thought she was dead and had found no confessor. Ma?tre Coppenole shows me I am mistaken. Then, laying his hand on Ma?tre Coppenoles shoulder: You were saying, Ma?tre Jacques -I said, Sire, that may-be you were right; that the peoples hour is not yet come with you. -Louis XI looked at him with his penetrating gaze. And when will that hour come, Ma?tre? -You will hear it strike. -By what clock, prithee? -Coppenole, with his quiet and homely self-possession, signed to the King to approach the window. Listen, Sire! There is here a donjon-keep, a bell-tower, cannon, townsfolk, soldiers. When the tocsin sounds, when the cannons roar, when, with great clamour, the fortress walls are shattered, when citizens and soldiers shout and kill each otherthen the hour will strike. -Louiss face clouded and he seemed to muse. He was silent for a moment, then, clapping his hand gently against the thick wall of the keep, as one pats the flank of a charger: -Ah, surely not, said he; thou wilt not be so easily shattered, eh, my good Bastille? -And turning abruptly to the undaunted Fleming: Have you ever seen a revolt, Ma?tre Jacques? -Sire, I have made one, answered the hosier. -How do you set about it, said the King, to make a revolt? -Oh, answered Coppenole, it is no very difficult matter. There are a hundred ways. First of all, there must be dissatisfaction in the townthats nothing uncommon. And next, there is the character of the inhabitants. Those of Ghent are prone to revolt. They ever love the son of the prince, but never the prince himself. Well, one fine morning, we will suppose, some one enters my shop and says to me: Father Coppenole, it is thus and thusthe Lady of Flanders wants to save her favourites, the chief provost has doubled the toll on green food, or something of the kindwhat you will. I throw down my work, run out of my shop into the street, and cry, sac! There is sure to be some empty cask about. I get upon it, and say in a loud voice the first thing that comes into my headwhats uppermost in my heartand when one is of the people, Sire, one has always something in ones heart. Then a crowd gets together; they shout, they ring the tocsin, the people arm themselves by disarming the soldiers, the market people join the rest, and off they march. And so it will always be, so long as there are lords in the manors, citizens in the cities, and peasants in the country. -And against whom do you rise thus? asked the King; against your provosts? against your lords? -Sometimes; it all depends. Against the duke too, on occasion. -Louis returned to his chair. Ah! here, he said with a smile, they have not got further than the provosts! -At the same instant Olivier le Daim entered the apartment. He was followed by two pages bearing the toilet necessaries of the King; but what struck Louis was to see him also accompanied by the Provost of Paris and the commander of the watch, who both appeared full of consternation. There was consternation, too, in the manner of the rancorous barber, but with an underlying satisfaction. -He was the first to speak. Sire, I crave pardon of your Majesty for the calamitous news I bring. -The King turned sharply round, tearing the mat under the feet of his chair. Whats that? -Sire, replied Olivier, with the malevolent look of one who rejoices that he has to deal a violent blow, it is not against the Provost of the Palais that this rising is directed. -Against whom, then? -Against you, Sire. -The aged King sprang to his feet, erect as a young man. -Explain thyself, Olivier! explain thyself! And look well to thy head, my Gossip; for I swear to thee by the cross of Saint-L?, that if thou speakest false in this matter, the sword that cut the throat of M. de Luxembourg is not so notched but it will manage to saw thine too. -It was a formidable oath. Never but twice in his life had Louis sworn by the cross of Saint-L?. -Olivier opened his mouth to answer. Sire -Down on thy knees! interrupted the King vehemently. Tristan, stand guard over this man! -Olivier went down on his knees. Sire, he said composedly, a witch was condemned to death by your Court of Parliament. She took sanctuary in Notre-Dame. The people want to take her thence by main force. Monsieur the Provost and Monsieur the Commander of the Watch are here to contradict me if I speak not the truth. It is Notre-Dame the people are besieging. -Ah! ah! murmured the King, pale and shaking with passion. Notre-Dame they besiege! Our Lady, my good mistress, in her own Cathedral! Rise, Olivier. Thou art right. I give thee Simon Radins office. Thou art right; it is me they attack. The witch is under the safeguard of the Church, the Church is under my safeguard. And Iwho thought all the while that it was only the provostand tis against myself! -Rejuvenated by passion, he began to pace the room with great strides. He laughed no more; he was terrible to look upon as he went to and frothe fox was become a hyena. He seemed choking with rage, his lips moved, but no word came, his fleshless hands were clenched. Suddenly he raised his head, his sunken eyes blazed full of light, his voice range like a clarion: Seize them, Tristan! Cut down the knaves! Away, Tristan, my friend! Kill! Kill! -This outburst over, he returned to his seat, and went on in a voice of cold and concentrated rage: Hither, Tristan. We have with us in this Bastille fifty lances of the Vicomte de Gif, which makes three hundred horses; you will take them. There is also a company of the archers of our bodyguard, under Monsieur de Chateaupers; you will take them. You are provost-marshal, and have the men of your provostry; you will take them. At the H?tel Saint-Pol you will find forty archers of the new guard of Monsieur the Dauphin; take them, and with all these you will speed to Notre-Dame. Ah, messieurs, the commons of Paris, do you fly thus in the face of the crown of France, of the sanctity of Notre-Dame, and the peace of this commonwealth! Exterminate, Tristan! exterminate! and let not one escape for Montfaucon! -Tristan bowed. Very good, Sire! And what am I to do with the witch? he added after a moments pause. -This question gave the King food for reflection. Ah, to be sure, said he, the witch? M. dEstouteville, what did the people want to do with her? -Sire, answered the Provost of Paris, I imagine, that as the people were come to drag her out of sanctuary in Notre-Dame, it is her impunity that offends them, and that they desire to hang her. -The King appeared to reflect profoundly; then, addressing himself to Tristan lHermite: -Very well, Compre; exterminate the people and hang the witch. -In other words, whispered Rym to Coppenole, punish the people for wanting to do a thing, and then do it yourself! -Very good, Sire, returned Tristan. And if the witch is still inside the Cathedral, are we to disregard the sanctuary and take her away? -Pasque-Dieu! the sanctuary, said the King, scratching his ear; and yet the woman must be hanged. -Then, as if an idea had suddenly occurred to him, he fell on his knees before his chair, took off his hat, laid it on the seat, and gazing devoutly at one of the little lead images with which it was encircled: Oh! he cried, clasping his hands, Our Lady of Paris, my gracious patroness, give me pardon, I will do it only this once. This criminal must be punished. I do assure you, Madame the Virgin, my good mistress, that it is a sorceress, unworthy of your kind protection. You know, Madame, that many very devout princes have trespassed on the privileges of the Church for the glory of God and the necessity of the state. Saint-Hugh, Bishop of England, permitted King Edward to seize a magician in his church. My master, Saint-Louis of France, transgressed for the like purpose in the Church of Saint-Paul, and Monsieur Alphonse, son of the King of Jerusalem, in the Church of the Holy Sepulchre itself. Pardon me, then, for this once, Our Lady of Paris! I will never again transgress in this manner, and I will give you a fair statue of silver, like that I gave last year to Our Lady of couys. So be it! -He made the sign of the cross, rose to his feet, replaced his hat, and turned to Tristan. Make all speed, Compre. Take M. de Chateaupers with you. You will sound the tocsin, crush the people, hang the witchthat is all. You will defray all the charges of the execution and bring me the account. Come, Olivier, I shall not go to bed to-night. Shave me. -Tristan lHermite bowed and left. The King then dismissed Rym and Coppenole with a wave of the hand. God keep you, my good Flemish friends. Go and take a little rest. The night is far advanced, and we are nearer the morning than the evening. -They both withdrew. On reaching their apartments under the escort of the captain of the Bastille, Coppenole remarked to Rym, Hum! Ive had enough of this coughing King. I have seen Charles of Burgundy drunk, but he was not near so wicked as Louis XI sick. -Ma?tre Jacques, returned Rym, that is because kings are not half so bloodthirsty in their wine as in their medicine-cups. -______________________ -1 Without steward or cup-bearer. -2 Pulse rapid, full, jerking, irregular. -Chapter 6 - The Pass-Word -On quitting the Bastille, Gringoire fled down the Rue Saint-Antoine with the speed of a runaway horse. Arrived at the Baudoyer Gate, he made straight for the stone cross in the middle of the square as if he discerned in the dark the figure of a man, clothed and hooded in black, sitting upon its steps. -Is that you, master? said Gringoire. -The figure rose. Death and hell! you drive me mad, Gringoire. The watch on the tower of Saint-Gervais has just called the half after one. -It is no fault of mine, returned Gringoire, but of the watch and the King. Ive had a narrow escape. I always miss being hanged within an ace. It is my predestination. -You miss everything, retorted the other. But come quickly now. Hast thou the pass-word? -Only think, master, I have seen the King. Ive just left him. He wears worsted breeches. It was an adventure, I can tell you! -Oh, clappering mill-wheel of words! whats thy adventure to me? Hast thou the truands pass-word? -I have it. Make yourself easy. Dagger in pouch. -Good! Without it we could not get through to the church; the truands block the streets. Luckily, they seem to have met with some opposition. We may yet arrive in time. -Yes, master; but how are we to gain entrance into Notre-Dame? -I have the key of the tower. -And how shall we get out again? -There is a small door at the back of the cloister opening on to the Terrain and the waterside. I have got the key, and I moored a boat there this morning. -I had a near shave of being hanged, repeated Gringoire. -Quick, then, let us be going! said the other; and both started off at full speed towards the city. -Chapter 7 - Chateaupers to The Rescue -The reader probably remembers the critical situation in which we left Quasimodo. The doughty hunchback, assailed on all sides, had lost, if not his courage, at least all hope of saving, not himselffor of that he took no thoughtbut the Egyptian. He ran distractedly along the gallery. Notre-Dame was on the point of being carried by the truands. Suddenly the thunder of galloping hoofs filled the adjacent streets, and with a long file of torches and a dense column of horsemen, lances down and bridles hanging loose, the furious sound swept into the Place like a hurricane. -France! France! Cut down the rabble! Chateaupers to the rescue! Provostry! Provostry! -These were, of course, the troops despatched by the King. -The startled truands faced about. -Quasimodo, though he heard nothing, saw the naked swords, the torches, the lances, the mass of cavalry, at the head of which he recognised Captain Ph?bus. He saw the confusion of the truands, the terror of some, the consternation of the stoutest-hearted among them, and the unexpected succour so revived his energy that he hurled back the foremost of the assailants who had already gained a footing on the gallery. -The truands bore themselves bravely, defending themselves with the energy of despair. Attacked on the flank from the Rue Saint-Pierre-aux-B?ufs, and in the rear from the Rue du Parvis, jammed against Notre-Dame, which they were attacking and Quasimodo still defendingat once besiegers and besiegedthey were in the peculiar position in which Count Henry dHarcourt found himself at the famous siege of Turin in 1640, between Prince Thomas of Savoy, whom he was besieging, and the Marquis de Langane, who, in turn, was blockading himTaurinum obsessor idem et obsessus1as his epitaph expresses it. -The mle was terrific. To wolves flesh dogs teeth, says Father Mathieu. The Kings horsemen, among whom Ph?bus de Chateaupers displayed great valour, gave no quarter, and they that escaped the lance fell by the sword. The truands, ill-armed, foamed and bit in rage and despair. Men, women, and children fastened themselves on the flanks and chests of the horses, clinging to them tooth and nail, like cats; others battered the faces of the archers with their torches; others, again, caught the horsemen by the neck in their iron bill-hooks, striving to pull them down. Those who fell, they tore to pieces. -One among them had a long and glittering scythe, with which, for a long time, he mowed the legs of the horses. It was an appalling sight. On he came, singing a droning song and taking long sweeping strokes with his deadly scythe. -At every stroke he laid around him a circle of severed limbs. He advanced in this manner into the thickest of the cavalry, calm and unhasting, with the even swing of the head and regular breathing of a reaper cutting a field of corn. It was Clopin Trouillefou. A volley of musketry laid him low. -In the meantime the windows had opened again. The burghers, hearing the war-cry of the Kings men, had taken part in the affray, and from every storey bullets rained upon the truands. The Parvis was thick with smoke streaked with the flashing fire of the musketry. Through it the facade of Notre-Dame was dimly discernible, and the tumble-down H?tel-Dieu, with a wan face or two peering frightened from its many windowed roofs. -At last the truands gave way. Exhaustion, want of proper arms, the alarming effect of this surprise, the volleys from the windows, the spirited charge of the Kings menall combined to overpower them. Breaking through the line of their assailants, they fled in all directions, leaving the Parvis heaped with their dead. -When Quasimodo, who had not for a moment ceased fighting, beheld this rout, he fell upon his knees and lifted his hands to heaven. Then, frenzied with joy, he ran to the stairs, and ascended with the swiftness of a bird to that cell, the approaches to which he had so intrepidly defended. He had but one thought nowto go and fall on his knees at the feet of her whom he had saved for the second time. -He entered the cellit was empty. -______________________ -1 Besieger of Turin and himself besieged. -BOOK XI -Chapter 1 - The Little Shoe -At the moment the truands attacked the Cathedral, Esmeralda was asleep. -But soon the ever-increasing uproar round the church, and the bleating of her goatawakened before herselfbroke these slumbers. She sat up, listened, looked around; then, frightened at the glare and the noise, hurried out of her cell to see what was the matter. The aspect of the Place, the strange visions moving in it, the disorder of this nocturnal assault, the hideous crowd dimly visible through the darkness, hopping about like a cloud of frogs, the hoarse croaking of the multitude, the scattered red torches flitting to and fro in the storm like will-o-the-wisps flitting over the misty face of a swampall seemed to her like some mysterious battle between the phantoms of the witches Sabbath and the stone monsters of the Cathedral. Imbued from her childhood with the superstitions of the gipsy tribe, her first idea was that she had happened unawares on the Satanic rites of the weird beings proper to the night. Whereupon she hastened back to cower in her cell, asking of her humble couch some less horrible nightmare. -But, by degrees, the first fumes of her terror cleared away from her brain, and by the constantly increasing noise, and other signs of reality, she discovered that she was beset, not by spectres, but by human beings. At this her fear changed; not in degree, but in kind. The thought of the possibility of a popular rising to drag her from her place of refuge flashed into her mind. The prospect of once more losing life, hope, Ph?bus, who still was ever-present in her dreams of the future, her utter helplessness, all flight barred, her abandonment, her friendless statethese and a thousand other cruel thoughts overwhelmed her. She fell upon her knees, her head upon her couch, her hands clasped upon her head, overcome by anxiety and terror; and gipsy, idolatress, and pagan as she was, began with sobs and tremblings to ask mercy of the God of the Christians, and pray to Our Lady, her hostess. For, even though one believe in nothing, there come moments in life in which one instinctively turns to the religion of the temple nearest at hand. -She remained thus prostrated for a considerable time, trembling, in truth, more than she prayed, frozen with terror at the breath of that furious multitude coming ever nearer; ignorant of the nature of the storm, of what was in progress, what they were doing, what they wanted; but having the presentiment of some dreadful issue. -In the midst of this agonizing uncertainty, she heard footsteps near her. She raised her head. Two men, one of whom was carrying a lantern, entered her cell. She uttered a feeble cry. -Fear nothing,said a voice which sounded familiar to her, it is I. -Who?she asked. -Pierre Gringoire. -The name reassured her. She raised her eyes and saw it was indeed the poet. But at his side stood a dark figure shrouded from head to foot which struck her dumb with fear. -Ah,said Gringoire in reproachful tones, Djali recognised me before you did. -In truth, the little goat had not waited for Gringoire to name himself. He had scarcely crossed the threshold before she began rubbing herself fondly against his knee, covering the poet with caresses and with white hairs, for she was casting her coat, Gringoire returning her endearments. -Who is that with you?asked the Egyptian in a low voice. -Make yourself easy,answered Gringoire, it is a friend of mine. -Then, setting down his lantern, the philosopher seated himself on the floor, clasping Djali enthusiastically in his arms. Oh, tis an engaging beast! More remarkable, no doubt, for its beauty and cleanliness than for its size; but ingenious, subtle, and lettered as a grammarian! Come, my Djali, let us see if thou hast not forgotten any of thy pretty tricks! How does Ma?tre Jacques Charmolue when -The man in black did not let him finish. He went up to him and pushed him roughly by the shoulder. Gringoire got up again. You are right,said he, I had forgotten that we were in haste. However, that is no reason, master, for hustling people so roughly. My dear pretty one, your life is in danger, and Djalis too. They want to hang you again. We are your friends, and have come to save you. Follow us. -Is that true?she cried. -Yes, quite true. Come without delay! -I will,she faltered; but why does not your friend speak? -Ah,said Gringoire, that is because his father and mother were somewhat fantastical people, and endowed him with a taciturn disposition. -She had perforce to content herself with this explanation. Gringoire took her by the hand, his companion picked up the lantern and walked ahead of them. The poor girl was bewildered with fear and let herself be led, the goat came skipping after them, so overjoyed at seeing Gringoire once more that she made him stumble at every other step by thrusting her horns between his legs. -Such is life,said the philosopher as he just missed falling flat; it is often our best friends that occasion our fall! -They rapidly descended the stairs of the towers, crossed the church, which was dark and totally deserted but echoing with the frightful uproar without, and issued by the Porte Rouge into the court-yard of the cloister. The cloister was deserted, the clergy having taken refuge in the bishops house, there to offer up their prayers together. The court-yard was empty save for a few terrified lackeys crouching in the darkest corners. They made their way to the small door leading out of the court-yard to the Terrain. The man in black opened it with a key he carried with him. Our readers are aware that the Terrain was a tongue of land enclosed by walls on the side next the city, belonging to the chapter of Notre-Dame, and forming the end of the island on the east, behind the church. They found this enclosure perfectly solitary. Here, even the noise in the air was sensibly less, the clamour of the assault reaching their ears confusedly and deadened. They could now hear the rustling of the leaves of the solitary tree planted at the point of the Terrain as the fresh breeze swept up from the river. Nevertheless, they were still very close to danger. The buildings nearest them were the bishops residence and the church. There were visible signs of great confusion within the bishops residence. Its dark mass was streaked with lights flitting from window to window, just as after burning a piece of paper, bright sparks run in a thousand fantastic lines across the dark mound of ashes. Beside it, the huge black towers of Notre-Dame rearing themselves over the long nave, sharply outlined against the vast red glow which filled the Parvis, looked like the gigantic andirons of some Cyclopean fire-place. -What was visible of Paris on all sides seemed to float in a mingled atmosphere of light and shadow, such as Rembrandt has in some of his backgrounds. -The man with the lantern walked straight to the point of the Terrain where, at the extreme edge of the water, were the decaying remains of a fence of stakes interlaced with laths, on which a low vine had spread its few starveling branches like the fingers of an open hand. Behind it, in the shadow of the fence, a little boat lay moored. The man motioned Gringoire and his companion to enter, and the goat jumped in after them. The man himself got in last. He cut the rope of the boat, pushed off from the shore with a long boat-hook, and seizing a pair of oars, seated himself in the bow and rowed with all his might out into mid-stream. The Seine runs very strong at this part, and he had considerable difficulty in clearing the point of the island. -Gringoires first care, on entering the boat, was to take the goat upon his knees. He settled himself in the stern, and the girl, whom the unknown man inspired with indefinable uneasiness, seated herself as close as possible to the poet. -As soon as our philosopher felt the boat in motion, he clapped his hands and kissed Djali between her horns. -Oh!he cried, now we are safe, all four of us!and added with the air of a profound thinker: We are indebted sometimes to fortune, sometimes to strategy, for the happy issue of a great undertaking. -The boat was making its way slowly across to the right bank. The gipsy girl regarded their unknown companion with secret terror. He had carefully shut off the light of his dark-lantern, and was now only dimly perceptible in the bow of the boat, like a shadowy phantom. His hood, which was still pulled down, formed a kind of mask to his face, and each time that in rowing he opened his arms, his long hanging black sleeves gave them the appearance of enormous bats wings. As yet he had breathed not a word. There was no sound in the boat but the regular splash of the oars and the rippling of the water against the sides of the skiff. -Upon my soul!suddenly exclaimed Gringoire, we are as lively as a company of horned-owls! We observe a silence of Pythagoreans or of fishes! Pasque-Dieu! my friends, I wish that some one would converse with me. The human voice is music in the human ear. That is not my own saying, but of Didymus of Alexandria, and an illustrious saying it is! Certes, Didymus of Alexandria was no mediocre philosopher. One word, my pretty oneonly one word, I entreat you. By the way, you used to make a droll little grimace, peculiar to yourself; do you make it still? You must know, my dear, that the Parliament has full jurisdiction over all places of sanctuary, and that you were in great peril in that little cell of yours in Notre-Dame? The little trochilus builds its nest in the crocodiles jaws. Master, heres the moon appearing again. If they only do not catch sight of us! We are performing a laudable act in saving mademoiselle, and yet they would string us up in the Kings name if they were to catch us. Alas, that every human action should have two handles! They blame in me what they crown in thee. One man admires C?sar, and abuses Catiline. Is that not so, master? What say you to this philosophy? I possess the philosophy of instinct, of nature, ut apes geometriam. What, no answer from anybody? You are both, it seems, in a very churlish mood! -You oblige me to do the talking alone. That is what we call in tragedy a monologue. Pasque-Dieu!I would have you know that I am just come from King Louis XI, and that I have caught that oath from himPasque-Dieu! they are keeping up a glorious howling in the city! Tis a bad, wicked old king. He is all wrapped in furs. He still owes me the money for my epithalamium, and he all but hanged me to-night, which would have greatly hindered my career. He is niggardly towards men of merit. He would do well to read the four books of Salvian of CologneAdversus Avaritiam. In good sooth, he is a king very narrow in his dealings with men of letters, and who commits most barbarous crueltiesa sponge laid upon the people, and sucking up their money. His thrift is as the spleen that grows big upon the wasting of the other members. And so the complaints against the hardness of the times turn to murmurs against the prince. Under this mild and pious lord of ours the gibbets are weighed down with corpses, the blocks rot with gore, the prisons burst like overfilled sacks. This king robs with one hand, and hangs with the other. He is the purveyor for Mme. Gabelle1 and M. Gibbet. The high are stripped of their dignities, and the low are increasingly loaded with fresh burdens. Tis an exorbitant prince. I like not this monarch. What say you, my master? -The man in black let the garrulous poet babble on. He was still struggling against the strong full current that separates the prow of the city from the poop of the ?le Notre-Dame, now called the ?le Saint Louis. -By-the-bye, master,Gringoire began again suddenly; just as we reached the Parvis through the raging crowd of truands, did your reverence remark the poor little devil whose brains that deaf ringer of yours was in the act of dashing out against the parapet of the gallery of kings? I am near-sighted, and could not recognise him. Who can it have been, think you? -The unknown answered not a word, but he ceased rowing abruptly; his arms fell slack as if broken, his head dropped upon his breast, and Esmeralda heard him sigh convulsively. -She started violently; she had heard sights like that before. -The boat, left to itself, drifted for a few moments with the stream; but the man in black roused himself at last, grasped the oars again, and set the boat once more up-stream. He doubled the point of the ?le Notre-Dame, and made for the landing-place at the hay wharf. -Ah!said Gringoire, we are passing the Logis Barbeau. Look, master, at that group of black roofs that form such quaint angles over there, just underneath that mass of low-hanging gray cloud, through which the moon looks all crushed and spread abroad like the yolk of an egg when the shell is broken. Tis a very fine mansion. It has a chapel crowned by a small dome which is wholly lined with admirably carved enrichments. Just above it, you can see the bell-tower, very delicately perforated. It also possesses a pleasant garden comprising a pond, an aviary, an echo, a mall, a labyrinth, and wild beast house, and many bosky paths very agreeable to Venus. Besides, theres a very naughty tree which they call the pander, because it cloaked the pleasures of a notorious princess and a certain Constable of Francea man of wit and gallantry. Alas! we poor philosophers are to a Constable of France as the cabbage or radish-bed to the garden of the Louvre. Well, what matters it after all? Life is a mixture of good and evil for the great even as for us. Sorrow is ever by the side of joy, the spondee beside the dactyl. Master, I must tell you that story of the Logis Barbeau some day; it had a tragical ending. It happened in 1319, in the reign of Philippe V, the longest reign of all the kings of France. The moral of the story is that the temptations of the flesh are pernicious and malign. Let our eyes not linger too long upon our neighbours wife, however much our senses may be excited by her beauty. Fornication is a very libertine thought. Adultery, a prying into the pleasant delights of another. Oh! the noise grows louder over there! -In truth, the uproar was increasing round Notre-Dame. They listened. They were plainly shouts of victory. Suddenly a hundred torches, their light flashing upon the helmets of men-at-arms, spread themselves rapidly over the church at every height, over the towers, the galleries, under the buttresses, appearing to be searching for something; and soon the distant shouts reached the ears of the fugitives: The gipsy! the witch! Death to the Egyptian! -The unhappy girl dropped her face in her hands, and the unknown began rowing furiously towards the bank. Meanwhile our philosopher cogitated rapidly. He clasped the goat in his arms, and edged gently away from the gipsy, who pressed closer and closer to his side as her only remaining protection. -Certainly Gringoire was on the horns of a cruel dilemma. He reflected that the goat too, by the existing legislation, was bound to be hanged if retaken, which would be a sad pity, poor little Djali! that two condemned females thus clinging on to him were more than he could manage, and that finally his companion asked for nothing better than to take charge of the gipsy girl. Nevertheless, a violent struggle went on in his mind, during which, like the Jupiter of the Iliad, he weighed the gipsy and the goat by turns in the balance, looking first at one and then at the other, his eyes moist with tears, while he muttered between his teeth, And yet I cannot save both of you! -The bumping of the boat against the landing-place shook him out of his musings. The sinister hubbub still resounded through the city. The unknown rose, advanced to the girl, and made as if to help her ashore; but she evaded him, and laid hold of Gringoires sleeve; whereat he, in turn, being fully occupied with the goat, almost repulsed her. She accordingly sprang ashore by herself, but in such a state of fear and bewilderment that she knew not what she did or whither she was going. She stood thus a moment, stupefied, gazing down at the swift flowing water. When she some-what recovered her senses, she found herself alone on the landing-stage with the unknown man. Gringoire had apparently availed himself of the moment of their going ashore to vanish with the goat among the labyrinth of houses of the Rue Grenier sur lEau. -The poor little gipsy shuddered to find herself alone with this man. She strove to speak, to cry out, to call to Gringoire, but her tongue clove to the roof of her mouth, and no sound issued from her lips. Suddenly she felt the hand of the unknown grasp hersa cold, strong hand. Her teeth chattered, she turned paler than the moonbeams that shone upon her. The man said not a word, but strode away in the direction of the Place de Grve, still holding her firmly by the hand. At that moment she had a dim sense of the irresistible force of destiny. All power of will forsook her; she let him drag her along, running to keep pace with him: the ground at this part of the quay rose somewhat, but to her they seemed to be rushing down an incline. -She looked on all sidesnot a single passenger to be seen; the quay was absolutely deserted. She heard no sound, she perceived no sign of life save in the glaring and tumultuous city, from which she was only separated by an arm of the river, and from which her own name reached her coupled with shouts of death. All the rest of Paris lay around her shadowy and silent as the grave. -Meanwhile the stranger was dragging her along in the same silence and at the same rapid pace. She had no recollection of any of the streets they traversed. Passing a lighted window she made a last effort, and stopping suddenly, screamed, Help! -The citizen at the window opened it, and showing himself in his night-shirt and a lamp in his hand, looked out stupidly on to the quay, muttered a few words which she could not catch, and closed his shutter once more. Her last ray of hope was extinguished. -The man in black proffered no remark; he held her fast and quickened his pace. She offered no further resistance, but followed him limp and hopeless. -From time to time she gathered sufficient strength to ask in a voice broken by the roughness of the pavement and the breathless haste of their motion: Who are you? Who are you?But there was no reply. -In this manner they presently reached an open square of considerable size. The moon shone faintly out; a sort of black cross was dimly visible standing in the middle. It was a gibbet. She saw this, and in a flash knew where she was. It was the Place de Grve. -The man stood still, turned towards her and lifted his hood. Oh,she stammered, petrified with horror, I knew it must be he! -It was the priest. He looked like a wraith in the spectral moonlight. -Listen,said he; and she shivered at the sound of the ill-omened voice that she had not heard for so long. Listen,he went on, speaking with that broken and gasping utterance which bespeaks the profoundest inward upheaval. We have arrived at our destination. I would speak with thee. This is the Grve; we have reached the extreme limit. Fate has delivered each of us into the hand of the other. Thou shalt have the disposing of my soul; I, of thy life. Here is a place and an hour beyond which there is no seeing. Listen to me, then. I will tell theebut first, name not thy Ph?bus to me. (And while he spoke thus he paced to and fro, like a man incapable of standing still, dragging her with him.) Speak not of him! Mark me, if thou utterest his name, I know not what I shall do, but it will be something terrible. -Having relieved his mind of this, he stood motionless, like a body finding its centre of gravity. But his agitation was in nowise diminished; his voice sank deeper and deeper. -Turn not away from me thus. Hear me; tis a matter of the utmost import. First, this is what has happenedtis no laughing matter, I warrant! What was I saying? Remind me! Ahthere is a decree of Parliament delivering thee over to execution again. I have but now succeeded in rescuing thee out of their hands. But they are on thy track. Behold! -He stretched his arm towards the city, where, in truth, the search seemed to be eagerly prosecuted. The noise of it drew nearer. The tower of the lieutenants house opposite the Grve was full of lights and bustle, and they could see soldiers running about the opposite quay with torches in their hands, shouting, The gipsy! Where is the gipsy? Death to her! death! -Thou seest plainly,resumed the priest, that they are in pursuit of thee and that I lie not. Oh, I love thee. Nay, speak not, open not thy lips, if it be to tell me that thou hatest me. I am resolved not to hear that again. I have just saved thee. Let me finish what I have to say. I can save thee altogether; I have prepared everything. It remains for thee to desire it. As thou wilt, so I can do. -He interrupted himself vehemently. No, that is not what I should have said! -With a hurried step, and making her hasten too, for he had retained his grasp of her arm, he walked straight to the gibbet, and pointing to it: -Choose between us,he said coldly. -She wrenched herself from his grasp and fell at the foot of the gibbet, clasping her arms round that grim pillar; and, half turning her beautiful head, gazed at the priest over her shoulder. It might have been a Madonna at the foot of the Cross. The priest had remained transfixed, his finger pointing to the gibbet, motionless as a statue. -At last the gipsy spoke: This is less abhorrent to me than you are. -He let his arm drop slowly, and bent his eyes upon the ground in deepest dejection. If these stones could speak,he murmured, they would say, Here is, indeed, a most unhappy man! -I love you,he resumed, and the girl, still kneeling at the gibbet, her long hair falling around her, let him speak without interrupting him. His tones were plaintive now and gentle, contrasting sadly with the harsh disdain stamped upon his features. Yes, in spite of all, tis perfectly true. Is there then nothing to show for this fire that consumes my heart! Alas! night and dayyes, girl, night and daydoes that deserve no pity? Tis a love of the night and the day, I tell youtis torture! Oh! my torment is too great, my poor child. Tis a thing worthy of compassion, I do protest to you. You see, I speak in all gentleness. I would fain have you cease to abhor me. Look you, when a man loves a woman, it is not his fault! Oh, my God! What! will you then never forgive me? will you hate me ever thus? And is this the end? That is what makes me wicked, look you, and horrible to myself. You will not even look at me. You are, may-be, thinking of something else while I stand here talking to you, and we both are trembling on the brink of eternity! But above all things, speak not to me of that soldier! What! I might fling myself at your knees, I might kiss, not your feetfor that you will not have, but the ground under your feet! I might sob like a child, might tear from my breast, not words, but my very heart, to tell you that I love youand all would be in vainall! And yet, there is nothing in your soul but what is tender and merciful. Loving kindness beams from you; you are all goodness and sweetness, full of pity and grace. Alas! your harshness is for me alone. Oh, bitter fate! -He buried his face in his hands. The girl could hear him weeping; it was the first time. Standing thus, and shaken by sobs, he made amore wretched and suppliant figure even than on his knees. He wept on for a while. -Enough,he said presently, the first violence of his emotion spent. I find no words. And yet I had well pondered what I would say to you. And now I tremble and shiver, I grow faint-hearted at the decisive moment. I feel that something transcendent wraps us round, and my tongue falters. Oh, I shall fall to the ground if you will not take pity on me, pity on yourself! Condemn us not both to perdition. Didst thou but know how much I love thee!what a heart is mine! the desertion of all virtue, the abandonment of myself! A doctor, I mock at science; a gentleman, I tarnish my name; a priest, I make of my missal a pillow of wantonnessI spit in the face of my Redeemer! And all for thee, enchantress; to be more worthy of thy hell! And yet thou rejectest the damned! Oh, let me tell thee allmore than this, something still more horrible, more horrible! -With these last words his manner became utterly distraught. He was silent a moment, then, in a stern voice and as if addressing himself: -Cain!he cried, what has thou done with thy brother? -There was a pause, and then he began again. What have I done with him, Lord? I took him, I reared him, I nourished him, loved him, idolized him, andI killed him! Yes, Lord, before my very eyes they dashed his head against the stones of thy house; and it was because of me, because of this woman, because of her -Madness gleamed from his sunken eyes; his voice dropped away; two or three times he repeated mechanically, and with long pauses between, like the last prolonged vibrations of the strokes of a bell, Because of herbecause of herAt last, though his lips still moved, no articulate sound came from them, then suddenly he fell in a heap like a house crumbling to pieces, and remained motionless on the ground, his head on his knees. -A faint movement of the girl, drawing away her foot from under him, brought him to himself. He slowly swept his hand over his haggard cheeks, and gazed for some moments at his fingers, surprised to find them wet. What,he murmured, have I been weeping? -He turned suddenly upon the gipsy with nameless anguish. -Woe is me! thou canst see me weep unmoved! Child, knowest thou that such tears are molten lava? Is it then indeed true, that in the man we hate nothing can melt us? Thou wouldst see me die and wouldst laugh. Oh, I cannot see thee die! One word, one single word of kindness! I ask not that thou shouldst say thou lovest me; tell me only that thou art willing I should save thee. That will suffice; I will save thee in return for that. If notoh, time flies! I entreat thee, by all that is sacred, wait not till I turn to stone again like this gibbet, that yearns for thee also! Remember that I hold both our destinies in my hand; that I am frenziedit is terriblethat I may let everything go, and that there lies beneath us, unhappy girl, a bottomless pit wherein my fall will follow thine to all eternity! One word of kindness! Say one word! but one word! -Her lips parted to answer him. He flung himself on his knees before her to receive with adoration the words, perchance of relenting, that should fall from them. -You are an assassin!she said. -The priest clasped her furiously in his arms and burst into a hideous laugh. -Good, then; yes, an assassin!he cried, and I will have thee. Thou wilt not have me for a slave; thou shalt have me for thy master. I will take my prey; I have a den whither I will drag thee. Thou shalt follow me; thou must follow me, or I will deliver thee up! Thou must die, my fair one, or be mine! belong to me, the priest, the apostate, the murderer! and this very night, hearest thou? Come! kiss me, little fool! The grave or my bed! -His eyes flashed with rage and lust. Froth stood on the lascivious lips that covered the girls neck with frenzied kisses. She struggled fiercely in his arms. -Bite me not, monster!she shrieked. Oh, the hateful, venomous monk! Let me go, or I tear out thy vile gray hairs and fling them in handfuls in thy face! -He turned red, then white, then loosed his hold on her with a darkling look. Thinking herself victorious, she went on: I tell thee I belong to my Ph?bus; that it is Ph?bus I love; Ph?bus, who is fair to look upon. Thou, priest, art old, thou art frightful. Get thee gone! -He uttered a sudden scream, like some poor wretch under the branding-iron. Die, then!said he, grinding his teeth. She caught his terrible look and turned to fly; but he seized her, shook her, threw her on the ground, and walked rapidly towards the corner of the Tour-Roland, dragging her after him along the pavement by her little hands. -Arrived at the corner of the Place, he turned round to her. For the last time, wilt thou be mine? -No! -The next moment, Gudule! Gudule!he cried in a loud voice, here is the gipsy! take thy revenge! -The girl felt herself suddenly seized by the arm. She looked up, a skeleton arm was stretched through the window in the wall and was holding her in a grip of iron. -Hold her fast!said the priest. It is the Egyptian woman escaped. Do not let her go; I go to fetch the sergeants. Thou shalt see her hang. -A guttural laugh from the other side of the wall made answer to these bloodthirsty words. The gipsy saw the priest hurry away towards the Pont Notre-Dame, from which direction came the clatter of horses hoofs. -The girl had recognised the evil-minded recluse. Panting with terror, she stove to free herself. In vain she writhed and turned in agony and despair, the other held her with incredible strength. The lean bony fingers that clutched her were clenched and met round her fleshthat hand seemed rivetted to her arm. It was more than a chain, more than an iron ring: it was a pair of pincers endowed with life and understanding, issuing from a wall. -Exhausted at last, she fell against the wall, and the fear of death came upon her. She thought of all that made life desirableof youth, the sight of the sky, all the varying aspects of nature, of love and Ph?bus, of all that was going from her and all that was approaching, of the priest who was even now betraying her, of the executioner he would bring, of the gibbet standing ready. Terror mounted even to the roots of her hair, and she heard the sinister laugh of the recluse as she hissed at her: Ha! ha! thou art going to be hanged! -She turned her fading eyes towards the window and saw the wolfish face of the sachette glaring at her through the bars. -What have I done to you?she gasped, almost past speaking. -The recluse made no answer, but fell to muttering in a sing-song, rasping, mocking tone: Daughter of Egypt! daughter of Egypt! daughter of Egypt! -The unfortunate Esmeralda let her head droop on her breast, understanding that this was no human being. -Suddenly, as if the gipsys question had taken all this time to reach her apprehension, the recluse exclaimed: -What hast thou done to me, sayest thou? Ah, what hast thou done to me, gipsy! Well, listen. I had a child Ihearest thou?I had a childa child, I tell thee! The fairest little daughter! My Agnesand she paused and kissed something distractedly in the gloom. Well, seest thou, daughter of Egypt, they took my child from me; they stole my child! That is what thou hast done to me! -To which the poor girl answered, like the lamb in the fable: Alas! perhaps I was not born then! -Oh, yes,rejoined the recluse, thou must have been born then. Thou wert one of them. She would be about thy agethou seest therefore! For fifteen years have I been here; fifteen years have I suffered; fifteen years have I been smiting my head against these four walls. I tell thee that they were gipsy women that stole her from medost thou hear?and that devoured her with their teeth. Hast thou a heart? Picture to thyself a child playing, sucking, sleeping so sweet, so innocent! Well, thatall thatwas what they stole from me, what they killed! The God in heaven knows it! To-day it is my turn; I shall eat of the Egyptian! Oh, that these bars were not so close, that I might bite thee! But my head is too big. The poor, pretty thing! while she slept! And if they did wake her as they took her away, she might scream as she would; I was not there! Ah, you gipsy mothers that ate my child, come hither now and look at yours!And she laughed again and ground her teeth the two actions were alike in that frenzied countenance. -Day was beginning to dawn. As the wan gray light spread gradually over the scene, the gibbet was growing more and more distinct in the centre of the Place. On the other side, in the direction of the Pont Notre-Dame, the poor girl thought she heard the sound of cavalry approaching. -Madame!she cried, clasping her hands and falling on her knees, dishevelled, wild, frantic with terror; Madame! have pity! They are coming. I never harmed you: will you see me die in this horrible manner before your very eyes? You have pity for me, I am sure. It is too dreadful. Let me fly; leave go of me, for pitys sake! I cannot die like that! -Give me back my child!said the recluse. -Mercy! mercy! -Give me back my child! -Let me go, in Heavens name! -Give me back my child! -Once again the girl sank down exhausted, powerless, her eyes already glazed, as if in death. -Alas!she stammered, you seek your child; II seek my parents. -Give me back my little Agnes!Gudule went on. Thou knowest not where she is? Then die! I will tell thee. I was a wanton, I had a child, they stole my child. It was the gipsies. Thou seest plainly that thou must die. When thy mother the gipsy comes to seek for thee, I shall say to her, Mother, behold that gibbet! Else give me back my child! Dost thou know where she is, my little girl? Here, let me show thee. Here is her shoe; tis all thats left to me of her. Dost know where the fellow to it is? If thou knowest, tell me, and I will go on my knees to fetch it, even to the other end of the world. -So saying, she thrust her other hand through the window and held up before the gipsy girl the little embroidered shoe. There was just light enough to distinguish its shape and its colour. -Let me see that shoe!said the gipsy with a start. Oh, God in heaven!And at the same time, with the hand she had free, she eagerly opened the little bag she wore about her neck. -Go to, go to!muttered Gudule; search in thy devils amulet -She broke off suddenly, her whole frame shook, and in a voice that seemed to come from the innermost depths of her being, she cried: My daughter! -For the gipsy had drawn from the amulet bag a little shoe the exact counterpart of the other. To the shoe was attached a slip of parchment, on which was written this couplet: -When thou the fellow of this shalt see, -Thy mother will stretch out her arms to thee. -Quicker than a flash of lightning the recluse had compared the two shoes, read the inscription on the parchment, then pressed her face, radiant with ineffable joy, against the cross-bars of the loophole, crying again: -My daughter! my daughter! -Mother!returned the gipsy girl. -Here description fails us. -But the wall and the iron bars divided them. Oh, the wall!cried the recluse. Oh, to see her and not embrace her! Thy handgive me thy hand! -The girl put her hand through the opening, and the mother threw herself upon it, pressing her lips to it, remaining thus lost to everything but that kiss, giving no sign of life but a sob that shook her frame at long intervals. For the poor mother was weeping in torrents in the silence and darkness of her cell, like rain falling in the night; pouring out in a flood upon that adored hand all that deep dark font of tears which her grief had gathered in her heart, drop by drop, during fifteen long years. -Suddenly she lifted her head, threw back her long gray hair from her face, and without a word began tearing at the bars of her window with the fury of a lioness. But the bars stood firm. She then went and fetched from the back of her cell a large paving-stone, which served her for a pillow, and hurled it against them with such force that one of the bars broke with a shower of sparks, and a second blow completely smashed the old iron cross-bar that barricaded the hole. Then, using her whole force, she succeeded in loosening and wrenching out the rusty stumps. There are moments when a womans hands are possessed of superhuman strength. -The passage clearedand it had taken her less than a minute to do itshe leaned out, seized her daughter round the waist, and drew her into the cell. -Come,she murmured, let me drag thee out of the pit. -As soon as she had her daughter in the cell, she set her gently on the ground; then catching her up in her arms again, as if she were still only the baby Agnes, she carried her to and fro in the narrow cell, intoxicated, beside herself with joy, shouting, singing, kissing her daughter, babbling to her, laughing, melting into tearsall at the same time, all with frenzied vehemence. -My daughter! my daughter!said she. I have my daughter againtis she! God has given her back to me. Hey there! come all of you! Is there anybody to see that Ive got my daughter? Lord Jesus, how beautiful she is! Thou hast made me wait fifteen years, oh, my God, but it was only that thou mightest give her back to me so beautiful. And the gipsy women had not eaten her! Who told me that they had? My little girlmy little onekiss me. Those good gipsies! I love the gipsies. So it is thou indeed? And it was that that made my heart leap every time thou didst pass by. And to think that I took it for hatred! Forgive me, my Agnes, forgive me! Thou thoughtest me very wicked, didst thou not? I love thee. Hast thou then that little mark still on thy neck? Let me see. Yes, she has it still. Oh, how fair thou art! Twas from me you got those big eyes, my lady. Kiss me. I love thee. What is it to me that other women have children? I can laugh at them now! Let them only come and look. Here is mine. Look at her neck, her eyes, her hair, her hand. Find me anything as beautiful as that! Oh, Ill warrant you shell have plenty of lovers, this one! I have wept for fifteen years. All my beauty that I lost has gone to her. Kiss me! -She said a thousand tender and extravagant things to her, the beauty of which lay in their tone, disarranged the poor childs garments till she blushed, smoothed her silken tresses with her hand, kissed her foot, her knee, her forehead, her eyes, went into raptures over everything, the girl letting her do as she would, only repeating at intervals, very low and with ineffable sweetness the word Mother! -Hark thee, my little girl,resumed the recluse, interrupting her words constantly with kisses, hark thee, I shall love thee and take good care of thee. We will go away from here. We are going to be so happy! I have inherited somewhat in Reimsin our country. Thou knowest Reims, thou canst not, thou wert too little. Couldst thou but know how pretty thou wert at four months oldsuch tiny feet that people came all the way from pernay, five leagues off, to see them. We shall have a field and a house. Thou shalt sleep in my own bed. Oh, my God! who would believe it? I have my daughter again! -Oh, mother!said the girl, finding strength at last to speak in her emotion, the gipsy woman spoke true. There was a good gipsy woman among our people who died last year, and who had always taken care of me like a foster-mother. It was she who hung this little bag round my neck. She used always to say to me: Child, guard this trinket well; tis a treasure; it will make thee find thy mother again. Thou wearest thy mother about thy neck! She foretold itthe gipsy woman. -Again the sachette clasped her daughter in her arms. Come, let me kiss thee; thou sayest that so prettily. When we are back in our own home, we will put the little shoes on the feet of an Infant Jesus in a church. We owe so much to the dear Virgin. Lord, what a sweet voice thou hast! When thou wert speaking to me just now it was just like music. Oh, Father in heaven, have I found my child again? Could any one believe such a story? Surely, nothing can kill one, for I have not died of joy.And she began clapping her hands and laughing as she cried: Oh, we are going to be so happy! -At that moment the cell resounded to the clank of arms and the galloping of horses, coming apparently from the Pont Notre-Dame and hastening nearer and nearer along the quay. The girl threw herself in anguish into the sachettes arms. -Save me! save me! Mother, they are coming! -The recluse grew pale. Oh, heaven! what dost thou say? I had forgotten; they are pursuing thee. What hast thou done? -I know not,answered the unhappy girl, but I am condemned to death. -To death!said Gudule, staggering as if struck by a thunder-bolt. Death!she repeated slowly, and fixed her daughter with wide staring eyes. -Yes, mother,repeated the girl distractedly, they want to kill me. They are coming to hang me. That gallows is for me. Save me! save me! Here they come; oh, save me! -The recluse stood for a moment as if petrified, then shook her head in doubt, and finally burst into a fit of laughterthe horrid laughter of her former days. -Oh, oh, no! tis a dream thou art telling me. What, I should have lost her for fifteen years, and then should find her, but only for a minute! And they would take her from me nownow that she is so beautiful, that she is a woman grown, that she speaks to me and loves me! And now they would come and devour her under my very eyeswho am her mother! Oh, no, such things are not possible. God would never permit it. -The cavalcade now apparently made a halt, and a distant voice could be heard saying: This way, Messire Tristan! The priest told us we should find her at the Rat-Hole.The tramp of horses commenced again. -The recluse started up with a cry of despair: Fly, fly, my child! It all comes back to me now. Thou art right. They seek thy death! Horror! Malediction!Fly! -She thrust her head through the window, but drew it back again hastily. -Stay where you are,she said in a quick, terrified whisper, convulsively pressing the hand of the girl, who was already more dead than alive. Keep still, do not breathe, there are soldiers everywhere. Thou canst not go out. It is too late. -Her eyes were dry and burning. For a few moments she did not speak, but paced her cell with rapid steps, stopping at intervals to pluck out whole strands of her gray hair and tear them with her teeth. -They are coming,she said suddenly; I will speak to them. Do thou hide in that corner. They will not see thee. I will tell them that thou hast escapedthat I let thee go! -She carried her daughter to a corner of the cell which could not be seen from outside; made her crouch down; disposed her carefully so that neither foot nor hand came beyond the shadow; spread her long black hair round her to cover the white robe, and set up the pitcher and flag-stone, the only furniture she had, in front of her, trusting that they would conceal her. This done, finding herself calmer, she knelt down and prayed. The day, which was only just dawning, left abundant darkness still in the Rat-Hole. -At this moment the voice of the priestthat voice from hellsounded close to the cell, crying: This way, Captain Ph?bus de Chateaupers! -At that name, uttered by that voice, Esmeralda, cowering in her corner, made a movement. -Do not stir!murmured Gudule. -She had scarcely spoken before a tumultuous crowd of men and horses stopped in front of the cell. The mother rose hastily and posted herself at the loophole to cover the aperture. She beheld a strong body of armed men, horse and foot, drawn up in the Grve. Their commander dismounted and came towards her. -Old woman,said this man, whose face wore a repulsive expression, we are seeking a witch to hang her. They tell us you had hold of her. -The poor mother assumed the most unconscious air she was able. -I do not quite take your meaning,she answered. -Tte-Dieu! Then what was this story of the crazy Archdeacons?said Tristan. Where is he? -My lord,said one of the soldiers, he has disappeared. -Go to, old hag,the commander went on; lie not to me. A witch was given into thy hand. What hast thou done with her? The recluse feared to deny altogether lest she should arouse suspicion, so she answered in a truthful but surly tone: -If you mean a strong young wench that they thrust into my hands a while ago, I can tell you that she bit me, and I let her go. Thats all I know. Leave me in peace. -The commander pulled a disappointed face. Let me have no lies, old spectre!he said. My name is Tristan lHermite, and I am the Kings Gossip. Tristan lHermite, dost thou hear?and he added, casting his eyes round the Place de Grve, tis a name that has echoes here. -And if you were Satan lHermite,retorted Gudule, gathering hope, I would have nothing different to say to you, nor would I be afraid of you! -Tte-Dieu!exclaimed Tristan, heres a vixen! So the witch girl escaped! And which way did she go? -Through the Rue du Mouton, I think,answered Gudule carelessly. -Tristan turned and signed to his men to prepare for resuming their march. The recluse breathed again. -Monseigneur,said an archer suddenly, ask the old beldame how it is that her window-bars are broken thus? -This question plunged the wretched mother back into despair. Still she did not lose all presence of mind. They were always so,she stammered. -Bah!returned the archer, only yesterday they made a fine black cross that inclined one to devotion. -Tristan glanced askance at the recluse. The beldame seems uneasy,he said. -The unhappy woman felt that all depended on her keeping up her self-possession, and so, with death in her heart, she began to laugh at them. Mothers are capable of efforts such as this. -Bah!said she, the man is drunk. Tis more than a year since the back of a cart laden with stones ran against my window and burst the bars. I mind me well how I railed at the driver. -Its true,said another archer, I was there. -There are always people to be found in all places who have seen everything. This unlooked-for testimony revived the spirits of the recluse, to whom this interrogatory was like crossing an abyss on the edge of a knife. -But she was doomed to a continual see-saw between hope and alarm. -If a cart had done that,resumed the first soldier, the stumps of the bars must have been driven inward, whereas they have been forced outward. -Ha! ha!said Tristan to the soldier, thou hast the nose of a cross-examiner at the Chatelet! Answer what he says, old woman! -Mon Dieu!she exclaimed, reduced to the last extremity, and bursting into tears in spite of herself; I swear to you, my lord, that it was a cart that broke those bars: you hear that man say he saw it. Besides, what has that to do with your gipsy? -Hm!growled Tristan. -Diable!continued the soldier, flattered by the provosts commendation; the iron looks quite fresh broken. -Tristan shook his head. Gudule turned pale. How long is it, say you, since the affair of the cart? -A month; a fortnight may-be, my lord; I do not remember. -At first she said above a year!remarked the soldier. -That looks queer!said the provost. -Monseigneur!she cried, still filling the window, and trembling lest suspicion should prompt them to put their heads through and look into the cell; monseigneur, I swear to you that it was a cart that broke this grating. I swear it by all the holy angels in paradise. If it was not a cart, may I go to everlasting perdition and deny my God! -Thou art very urgent in that oath of thine!said Tristan with his inquisitorial glance. -The poor creature felt her assurance ebbing fast away. She was making blunders, and had a terrible consciousness that she was not saying what she should have said. -Here another soldier came up, crying: Monseigneur, the old wife lies. The witch cannot have got away by the Rue du Mouton, for the chain was across the street all night, and the watchman saw no one pass. -What hast thou to say to that?asked Tristan, whose countenance grew every moment more forbidding. -She strove to offer a bold front to this fresh incident. Why, monseigneur, I do not know; I must have made a mistake, I suppose. In fact, now I come to think of it, I believe she crossed the water. -Thats at the opposite side of the Place,said the provost. And then its not very likely that she should want to return to the city where they were making search for her. Thou liest, old woman! -Besides,added the first soldier, theres no boat either on this side or the other. -She will have swam across then,said the recluse, fighting her ground inch by inch. -Do women swim?said the soldier. -Tte-Dieu! old woman, thou liest, thou liest!cried Tristan angrily. Ive a good mind to leave the witch and take thee instead. A little quarter of an hours question would soon drag the truth out of thy old throat. Come! Thou shalt go along with us! -She caught eagerly at these words. -As you will, my lord; do as you say. The question! I am quite ready to submit to it. Carry me with you. Quick! let us go at once!and meantime,thought she, my daughter can escape. -Mort-Dieu!said the provost, what a thirst for the rack! This crazy old wifes quite beyond my comprehension. -A grizzled old sergeant of the watch now stepped out of the ranks and addressed the provost. Crazy indeed, monseigneur! If she let the gipsy go, tis not her fault, for she has no love for gipsy women. For fifteen years Ive held the watch here, and every night I hear her calling down curses without end on these Bohemian women. If the one were looking for is, as I believe, the little dancer with the goat, she hated her beyond all the rest. -Gudule gathered up her strength: -Yes, her beyond all the rest,she repeated. -The unanimous testimony of the men of the watch confirmed what the old sergeant had said. Tristan lHermite, despairing of getting anything out of the recluse, turned his back on her, and, with irrepressible anxiety, she saw him slowly return to his horse. -Come!he growled between his teeth. Forward! we must continue the search. I will not sleep till the gipsy has been hanged. -Nevertheless, he lingered a moment before mounting. Gudule hung between life and death as she saw him scanning the Place with the restless look of the hound that instinctively feels himself near the lair of his quarry, and is reluctant to go away. At last he shook his head, and sprang into the saddle. -Gudules heart, so horribly contracted, now expanded, and she whispered, with a glance towards her daughter, whom she had not ventured to look at since the arrival of her pursuers, Saved! -All this time the poor child had remained in her corner, without breathing, without moving a muscle, death staring her in the face. She had lost no word of the scene between Gudule and Tristan, and each pang of her mothers had echoed in her own heart. She had heard each successive crack of the thread that held her suspended over the abyss, and twenty times she thought to see it snap. Only now did she begin to take breath and feel the ground steady under her feet. -At this moment she heard a voice call to the provost: Corb?uf! Monsieur the Provost, its none of my business as a man-at-arms to hang witches. The rabble populace is put down; I leave you to do your own work alone. You will permit me to return to my company, who are meanwhile without a captain. -The voice was that of Ph?bus de Chateaupers. What passed in her breast is impossible to describe. He was there, her friend, her protector, her safeguard, her refugeher Ph?bus! She started to her feet, and before her mother could prevent her had sprung to the loophole, crying: -Ph?bus! To me, my Ph?bus! -Ph?bus was no longer there. He had just galloped round the corner of the Rue de la Coutellerie. But Tristan had not yet gone away. -The recluse rushed at her daughter with a snarl of rage and dragged her violently back, her nails entering the flesh of the girls neck. But the mother turned tigress has no thought of careful handling. Too late. Tristan had seen it all. -H! h!he chuckled with a grin that bared all his teeth and made his face wolfish; two mice in the trap! -I suspected as much,said the soldier. Tristan slapped him on the shoulder. Thou are a good cat! Now, then,he added, where is Henriet Cousin? -A man, having neither the dress nor the appearance of a soldier, stepped out from their ranks. He wore a suit half gray, half brown, with leather sleeves, and carried a coil of rope in his great hand. This man was in constant attendance on Tristan, who was in constant attendance on Louis XI. -Friend,said Tristan lHermite, I conclude that this is the witch we are in search of. Thou wilt hang me that one. Hast thou thy ladder? -There is one under the shed at the Maison-aux-Piliers,answered the man. Is it at the gallows over there were to do the job?he continued, pointing to the gibbet. -Yes. -So, ho!said the man, with a coarse laugh more brutal even than the provosts, we shall not have far to go! -Make haste,said Tristan, and do thy laughing afterward. -Since the moment when Tristan had seen her daughter, and all hope was lost, the recluse had not uttered a word. She had thrown the poor girl, half dead, into a corner of the cell and resumed her post at the window, her two hands spread on the stone sill like two talons. In this attitude she faced the soldiers unflinchingly with a gaze that was once more savage and distraught. As Henriet Cousin approached the cell, she fixed him with such a wild beast glare that he shrank back. -Monseigneur,said he, turning back to the provost, which must I take? -The young one. -So much the better; the old one seems none too easy. -Poor little dancer!said the sergeant of the watch. -Henriet Cousin advanced once more to the window. The mothers eye made his own droop. -Madame,he began timidly -She interrupted him in a whisper of concentrated fury: -What wilt thou? -It is not you,he said, but the other one. -What other one? -The young one. -She shook her head violently. There is nobody! nobody! nobody!she cried. -Yes, there is!returned the hangman, as you very well know. Let me take the girl. I mean no harm to you. -Ah! ha!she said, with a wild laugh; you mean no harm to me? -Let me take the other, good wife; tis the provosts orders. -There is nobody else,she repeated distractedly. -But I tell you there is!retorted the hangman. We all saw the two of you. -Thou hadst best look, then,said the recluse with a mad chuckle. Thrust thy head through the window. -The hangman considered the nails of the mother, and dared not. -Haste thee now!cried Tristan, who had drawn up his men in a circle round the Rat-Hole, and stationed himself on horseback near the gibbet. -Henriet returned to the provost in perplexity. He laid the coil of rope on the ground, and was twisting his cap nervously in his hands. -Monseigneur,he asked, how must I get in? -By the door. -There is none. -Then by the window. -It is too narrow. -Widen it, then,said Tristan impatiently. Hast thou no pickaxes? -The mother, still on guard at the opening to her den, watched them intently. She had ceased to hope, ceased to wish for anything. All she knew was that she would not have them take her daughter from her. -Henriet Cousin went and fetched the box of executioners tools from the shed of the Maison-aux-Piliers; also, from the same place, the double ladder, which he immediately set up against the gibbet. Five or six of the provosts men provided themselves with crowbars and pickaxes, and Tristan accompanied them to the window of the cell. -Old woman,said the provost in stern tones, give up the girl to us quietly. -She gazed at him vacantly. -Tte-Dieu!exclaimed Tristan, why dost thou hinder us from hanging this witch as the King commands? -The wretched creature broke into her savage laugh again. -Why do I hinder you? She is my daughter. -The tone in which she uttered these words sent a shudder even through Henriet Cousin himself. -I am sorry,returned the provost. But it is the good pleasure of the King. -Whereat she cried, her dreadful laugh ringing louder than before: -What is he to methy King? I tell thee it is my daughter. -Break through the wall!commanded Tristan. -To do this it was only necessary to loosen a course of stone underneath the loophole. When the mother heard the picks and lever sapping her fortress, she uttered a blood-curdling cry, and then started running round and round her cell with startling quicknessa wild-beast habit she had learned from her long years of confinement in that cage. She said no word, but her eyes blazed. The soldiers felt their blood run cold. -Suddenly she snatched up her stone in both hands, laughed, and hurled it at the workmen. The stone, ill-thrown, for her hands were trembling, touched no one, but fell harmless at the feet of Tristans horse. She gnashed her teeth. -Meanwhile, though the sun had not yet risen, it was broad daylight, and the old, moss-grown chimneys of the Maison-aux-Piliers flushed rosy red. It was the hour when the windows of the earliest risers in the great city were thrown cheerfully open. A countryman or so, a few fruit-sellers, going to the markets on their asses, were beginning to cross the Grve, and halted for a moment to gaze with astonishment at the group of soldiers gathered about the Rat-Hole, then passed on their way. -The recluse had seated herself on the ground close beside her daughter, covering her with her body, her eyes fixed, listening to the poor child, who, as she lay motionless, kept murmuring the one word, Ph?bus! Ph?bus! -As the work of demolition seemed to advance, so the mother drew mechanically farther back, pressing the girl closer and closer against the wall. All at once she saw the stone, from which she had never taken her eyes, begin to give way, and hear the voice of Tristan urging on the men. At this she awoke from the kind of stupor into which she had fallen for a few moments, and cried aloud; and her voice as she spoke now lacerated the ear like the rasp of a saw, now faltered and choked as if every kind of execration crowded to her lips to burst forth at once. Ho, ho, ho! but tis horrible! Robbers! brigands! Are ye truly coming to take my daughter from me? I tell you, tis my own child! Oh, cowards! oh, hangmans slaves! miserable hired cut-throats and assassins! Help! help! Fire! And can they have the heart to take my child from me thus? Who is it then they call the good God in heaven? -Then, addressing herself to Tristan, foaming, glaring, bristling, on all-fours like a panther: Now come and dare to take my daughter from me. Dost thou not understand when this woman tells thee tis her daughter? Dost thou know what it is to have a child, eh, thou wolf? Hast thou never lain with thy mate? Hast never had a cub by her? -And if thou hast little ones, when they howl, is there never an answering stir within thee? -Down with the stone,said Tristan; it is loose enough now. -The crowbars heaved the heavy block. It was the mothers last bulwark. She threw herself upon it, trying to hold it in its place; she furrowed the stone with her nailsin vain; the great mass, displaced by half a dozen men, escaped her grasp and slid slowly to the ground along the iron levers. -The mother, seeing the breach effected, then cast herself across the opening, barring it with her body, writhing, striking her head against the floor, and shrieking in a voice so hoarse with anguish and fatigue that the words were hardly articulate: -Help! Fire! Help! -Now, then, take the girl,said Tristan, imperturbably. -The mother faced the soldiers with so menacing a glare that they seemed more fain to retreat than advance. -Forward!cried the provost. Henriet Cousinyou! -No one advanced a step. -The provost rapped out an oath. Tte-Christ! my soldiers afraid of a woman! -Monseigneur,ventured Henriet, you call that a woman? -She has a bristling mane like a lion,said another. -Forward!repeated the provost. The gap is large enough. Enter three abreast, as at the breach of Pontoise. Lets make an end of it, death of Mahomet! The first man that draws back, I cleave him in two! -Fixed thus between the devil and the deep sea, the soldiers hesitated a moment, then, deciding for the lesser evil, advanced upon the Rat-Hole. -When the recluse saw this, she swept back her long hair from her eyes, struggled to her knees, and dropped her bleeding and emaciated hands upon them. Great tears welled up one by one to her eyes and rolled down a long furrow in her cheeks, like a torrent down the bed it has hollowed out. And then she began to speak, but in a voice so suppliant, so gentle, so submissive and heart-breaking that more than one hardened old fire-eater in Tristans company furtively wiped his eyes. -Good sirs,said she, messieurs the sergeants, one word. There is a thing I must tell you. This is my daughter, look youmy dear little child who was lost to me! Listen, tis quite a story. It may surprise you, but I know messieurs the sergeants well. They were always good to me in the days when the little urchins threw stones at me because I was a wanton. Look you; you will leave me my child when you know all! I was a poor wanton. The gipsies stole her from meby the same token I have kept her shoe these fifteen years. Look, here it is. She had a foot like that. At Reims. La Chantefleurie! Rue Folle-Peine! Perhaps you knew of this? It was I. In your young days; then it was a merry time, and there were merry doings! You will have pity on me, wont you, good sirs? The gipsies stole her, and hid her from me for fifteen years. I thought her dead. Picture to yourself, my good friends, that I thought her dead. I have passed fifteen years here, in this stone cave, without any fire in winter. That is hard. The poor, sweet little shoe! I cried so long to God that he heard me. You will not take her from me, I am sure. Even if twere me you wanted, I would not mind; but a child of sixteen! Leave her a little while longer to live in the sunshine! What has she done to you? Nothing at all. Nor I either. If you only knewI have no one but her. I am oldthis is a blessing sent me from the Holy Virgin! And then, you are all so good! you did not know that it was my daughter; but now you know. Oh, I love her! Monsieur the Chief Provost, I would rather have a stab in my body than a scratch on her little finger! You have the air of a kind gentleman! What I tell you now explains the whole matter, surely? Oh! if you have a mother, siryou are the captain, leave me my child! See how I entreat you on my knees, as we pray to Jesus Christ! I ask not alms of any one. Sirs, I come from Reims; I have a little field from my uncle Mahiet Pradon. I am not a beggar. I want nothingnothing but my child! Oh, I want to keep my child! The good God, who is master over all, has not given her back to me for nothing. The -King!you say the King! It cannot give him much pleasure that they should kill my daughter! Besides, the King is good! She is my daughter; mine, not the Kings! She does not belong to him! I will go away! we will both go. After all, just two women passing along the roada mother and her daughter; you let them go their way in peace! Let us go; we come from Reims. Oh, you are kind, messieurs the sergeants. I have nothing to say against you. You will not take my darling; it is not possible! Say it is not possible! My child! My child! -We shall not attempt to convey any idea of her gestures, her accent, the tears that trickled over her lips as she spoke, her clasping, writhing hands, the heart-breaking smiles, the agonized looks, the sighs, the moans, the miserable and soul-stirring sobs she mingled with these frenzied, incoherent words. When she ceased, Tristan lHermite knit his brows, but it was to hide a tear that glistened in his tigers eye. He conquered this weakness, however, and said brusquely: It is the Kings will. -Then leaning down to Henriet Cousins ear, he whispered hurriedly, Do thy business quickly.It may be that the redoubtable provost felt his heart failing himeven his. -The hangman and the sergeant accordingly entered the cell. The mother made no attempt at resistance; she only dragged herself over to her daughter and threw herself distractedly upon her. -The girl saw the soldiers advancing towards her, and the horror of death revived her senses. -Mother!she cried in a tone of indescribable anguish; oh, mother! they are coming! defend me! -Yes, yes, dear love, I am defending thee!answered the mother in expiring tones; and clasping her frantically in her arms, she covered her face with kisses. To see them together on the ground, the mother thus protecting her child, was a sight to wring the stoniest heart. -Henriet Cousin took hold of the gipsy girl under her beautiful shoulders. At the touch of that hand she gave a little shuddering cry and swooned. The executioner, from whose eyes big tears were dropping, would have carried her away, and sought to unclasp the mothers arms, which were tightly coiled about her daughters waist, but she held on to her child with such an iron grasp that he found it utterly impossible to separate them. He therefore had to drag the girl out of the cell, and the mother along with her. The mothers eyes, too, were closed. -The sun rose at this moment, and already there was a considerable crowd of people in the Place looking from a distance at what was being dragged over the ground to the gibbet. For this was Tristans way at executions. His one idea was to prevent the curious from coming too near. -There was nobody at the windows. Only, in the far distance, on the summit of that tower of Notre-Dame which looks towards the Grve, two men, their dark figures standing out black against the clear morning sky, appeared to be watching the scene. -Henriet Cousin stopped with his burden at the foot of the fatal ladder, and with faltering breath, such a pity did he think it, he passed the rope round the girls exquisite neck. At the horrible contact of the hempen rope, the poor child opened her eyes and beheld the skeleton arm of the gibbet extended over her head. She struggled to free herself, and cried out in an agonized voice: No! no! I will not! I will not!The mother, whose head was buried in her daughters robe, said no word, but a long shudder ran through her whole frame, and they could hear the frenzied kisses she bestowed upon her child. The hangman seized this moment to wrench asunder the arms clasped round the doomed girl, and whether from exhaustion or despair, they yielded. He then lifted the girl to his shoulder, where the slender creature hung limp and helpless against his uncouth head, and set foot upon the ladder to ascend. -At this moment the mother, who had sunk in a heap on the ground, opened her eyes wide. A blood-curdling look came over her face; without a word she started to her feet, and in a lightning flash flung herself, like a wild beast on its prey, on the hangmans hand, biting it to the bone. The man howled with pain; the others ran to his assistance, and with difficulty released his bleeding hand from the mothers teeth. Still she uttered no sound. They thrust her back with brutal roughness, and she fell, her head striking heavily on the stones. They raised her up; she fell back again. She was dead. -The hangman, who had kept his hold on the girl, began once more to ascend the ladder. -______________________ -1 The salt tax. -Chapter 2 - La Creatura Bella Bianco Vestita.Dante -When Quasimodo saw that the cell was empty, that the gipsy girl was gone, that while he was defending her she had been carried off, he clutched his hair with both hands and stamped with surprise and grief; and then set off running, searching the Cathedral from top to bottom for his gipsy, uttering strange unearthly cries, strewing the pavement with his red hair. It was the very moment at which the Kings archers forced their victorious way into Notre-Dame, likewise on the hunt for the gipsy. Poor deaf Quasimodo, never suspecting their sinister intentions (he took the truands to be the enemies of the gipsy girl), did his utmost to assist them. It was he who led Tristan lHermite into every possible nook and cranny, opened secret doors, double bottoms of altars, hidden sacristies. Had the unhappy girl still been there, it would have been Quasimodo himself who betrayed her into the hands of the soldiers. -When Tristan, who was not easily discouraged, gave up the search as hopeless, Quasimodo continued it alone. Twenty times, a hundred times over, did he go through the church, from end to end, from top to bottom; ascending, descending, running here, calling there, peering, searching, thrusting his head into every hole, holding up a torch under every vault, desperate, frenzied, moaning like a beast that has lost his mate. -At length, when he had made himself surequite, quite surethat she was gone, that it had come to the worst, that they had stolen her from him, he slowly reascended the lower stairsthose stairs which he had mounted so nimbly and triumphantly on the day he had saved her. He now went over the same ground with dejectedly drooping head, voiceless, tearless, with bated breath. The church was once more solitary and silent. The archers had quitted it to pursue their search for the sorceress in the city. Quasimodo, left alone now in the vast Cathedral, so thronged and tumultuous but a moment before, made his way to the cell where the gipsy girl had slept for so many weeks under his watchful protection. -As he drew near it he tried to delude himself that he might find her there after all. When, on reaching the bend of the gallery that looks down on the roof of the side aisle, he could see the narrow cell with its little window and its little door, lying close under one of the great buttresses, like a birds nest under a bough, the poor creatures heart failed him, and he had to lean against the pillar to save himself from falling. He pictured to himself that perchance she had returned; that some good genius had brought her back; that this little nest was too quiet, too safe, to cosy for her not to be there; and he dared not venture a step nearer for fear of dispelling his illusion. Yes, he said to himself, may-be she sleeps, or she is at her prayers. I will not disturb her. -At last he summoned up courage, advanced on tip-toe, looked in, entered. Empty! The cell was still empty. Slowly the unhappy man made the tour of the little place, lifted up her pallet and looked beneath it, as if she could be hiding between it and the stone floor, shook his head, and stood staring stupidly. Suddenly he furiously stamped out his torch, and without uttering a word or breathing a sigh, he hurled himself with all his strength head-foremost against the wall and fell senseless to the ground. -When he came to himself, he flung himself on the bed, rolling on it and pressing frenzied kisses on the pillow, which still bore the imprint of her head. Here he lay for some minutes, motionless as the dead, then rose, panting, crazed, and fell to beating his head against the wall with the appalling regularity of the stroke of a clock and the resolution of a man determined to break his skull. At length he dropped down exhausted, then crawled outside the cell, and remained crouching, motionless, opposite to the door for a full hour, his eyes fixed on the deserted cell, sunk in a gloomier, more mournful reverie than a mother seated between an empty eradle and a tenanted coffin. He spoke no word; only at intervals a deep sob convulsed his whole frame, but a sob that brought no tears, like the silent flashes of summer lightning. -It was then that, striving amid his despairing memories to divine who could possibly have been the unforeseen ravisher of the gipsy girl, the thought of the Archdeacon flashed into his mind. He remembered that Dom Claude alone possessed a key of the stair-case leading to the cell; he recalled his nocturnal attempts upon Esmeralda, the first of which he, Quasimodo, had assisted, the second prevented. He called to mind a thousand various details, and soon was convinced that it was the Archdeacon who had taken the gipsy from him. Nevertheless, such was his reverence for the priest, so deeply were gratitude, devotion, and love for this man rooted in his heart, that they resisted, even at this supreme moment, the fangs of jealousy and despair. The moment that Claude Frollo was concerned, the bloodthirsty, deadly resentment he would have felt against any other individual was turned in the poor bell-ringers breast simply into an increase of his sorrow. -At the moment when his thoughts were thus fixed upon the priest, as the dawn was beginning to gleam upon the buttresses, he beheld on the upper story of the Cathedral, at the angle of the balustrade that runs round the outside of the chancel, a figure advancing in his direction. He recognised itit was the Archdeacon. -Claude was moving with a slow and heavy step. He did not look before him as he walked, his face was turned aside towards the right bank of the Seine, and he held his head up as if endeavouring to obtain a view of something across the roofs. The owl has often that sidelong attitude, flying in one direction while it gazes in another. In this manner the priest passed along above Quasimodo without catching sight of him. -The deaf spectator, petrified by this sudden apparition, saw the figure disappear through the door leading to the stair of the northern tower, which, as the reader is aware, commands a view of the H?tel-de-Ville. -Quasimodo rose and followed the Archdeacon, mounting the stair after him to find out why the priest was going there. Not that the poor bell-ringer had any definite idea of what he himself was going to do or say, or even what he wanted. He was full of rage and full of dread. The Archdeacon and the Egyptian clashed together in his heart. -On reaching the top of the tower, and before issuing from the shade of the stair-case, he cautiously investigated the position of the priest. The Archdeacon had his back towards him. An openwork balustrade surrounds the platform of the steeple; the priest, whose eyes were fixed upon the town, was leaning forward against that side of the square balustrade which faces the Pont Notre-Dame. -With noiseless tread Quasimodo stole up behind him, to see what he was so intently gazing at, and the priests attention was so entirely absorbed elsewhere that he did not hear the step of the hunchback near him. -It is a magnificent and enchanting spectacleand yet more so in those daysthat view of Paris from the summit of the towers of Notre-Dame, in the sparkling light of a summers dawn. It must have been a day early in July. The sky was perfectly serene; a few lingering stars, here and there, were slowly fading, and eastward, in the clearest part of the sky, hung one of great brilliancy. The sun was on the point of rising. Paris was beginning to stir, the endless variety of outline presented by its building on the eastern side showing up vividly in the singularly pure white light, while the gigantic shadow of the steeples crept from roof to roof, traversing the great city from one end to the other. Already voices and sounds were arising in several quarters of the town; here the clang of a bell, there the stroke of a hammer, elsewhere the complicated clatter of a cart in motion. The smoke from chimneys curled up here and there out of the mass of roofs, as if through the fissures of some great solfatara. The river, swirling its waters under its many bridges, round the points of innumerable islands, was diapered in shimmering silver. Around the city, outside the ramparts, the view melted into a great circle of fleecy vapour, through which the indefinite line of the plain and the soft undulation of the hills was faintly visible. All sorts of indeterminate sounds floated over the half-awakened city. In the east, a few downy white flakes, plucked from the misty mantle of the hills, fled across the sky before the morning breeze. -Down in the Parvis, some housewives, milk-pot in hand, were pointing out to one another in astonishment the extraordinary condition of the great door of Notre-Dame, and the two streams of lead congealed between the fissures of the stones. This was all that remained of the tumult of the night before. The pile kindled by Quasimodo between the towers was extinct. Tristan had already cleared the dbris from the Place and thrown the bodies into the Seine. Kings like Louis XI are careful to clean the pavements with all expedition after a massacre. -Outside the balustrade of the tower, immediately underneath the spot where the priest had taken up his position, was one of those fantastically carved gargoyles which diversify the exterior of Gothic buildings, and in a crevice of it, two graceful sprigs of wall-flower in full bloom were tossing, and, as if inspired with life by the breath of the morning, made sportive salutation to each other, while from over the towers, far up in the sky, came the shrill twittering of birds. -But the priest neither saw nor heard anything of all this. He was one of those men for whom there are neither mornings, nor birds, nor flowers. In that immense horizon spread around him, in such infinite variety of aspect, his gaze was concentrated upon one single point. -Quasimodo burned to ask him what he had done with the gipsy girl; but the Archdeacon seemed at that moment altogether beyond this world. He was evidently in one of those crucial moments of life when the earth itself might fall in ruins without our perceiving it. -With his eyes unwaveringly fixed upon a certain spot, he stood motionless and silent; but in that silence and that immobility there was something so appalling that the dauntless bell-ringer shuddered at the sight, and dared not disturb him. All that he didand it was one way of interrogating the priestwas to follow the direction of his gaze, so that in this way the eye of the poor hunchback was guided to the Place de Grve. -Thus he suddenly discovered what the priest was looking at. A ladder was placed against the permanent gibbet; there were some people in the Place and a number of soldiers; a man was dragging along the ground something white, to which something black was clinging; the man halted at the foot of the gibbet. -Here something took place which Quasimodo could not very distinctly see; not that his eye had lost its singularly long vision, but that there was a body of soldiers in the way, which prevented him seeing everything. Moreover, at that instant the sun rose and sent such a flood of light over the horizon that it seemed as if every point of Parisspires, chimneys, gableswere taking fire at once. -Now the man began to mount the ladder, and Quasimodo saw him again distinctly. He was carrying a female figure over his shouldera girlish figure in white; there was a noose round the girls neck. Quasimodo recognised her. It was She! -The man arrived with his burden at the top of the ladder. There he arranged the noose. -At this the priest, to have a better view, placed himself on his knees on the balustrade. -Suddenly the man kicked away the ladder with his heel, and Quasimodo, who for some minutes had not drawn a breath, saw the hapless girl, with the feet of the man pressing upon her shoulders, swinging from the end of the rope, some feet from the ground. The rope made several turns upon itself, and Quasimodo beheld horrible contortions jerking the body of the gipsy girl. The priest, meanwhile, with outstretched neck and starting eyeballs, contemplated this frightful group of the man and the girlthe spider and the fly! -At the moment when the horror of the scene was at its height, a demoniacal laugha laugh that can only come from one who has lost all semblance of humanityburst from the livid lips of the priest. -Quasimodo did not hear that laugh, but he saw it. Retreating a few paces behind the Archdeacon, the hunchback suddenly made a rush at him, and with his two great hands against Dom Claudes back, thrust him furiously into the abyss over which he had been leaning. -The priest screamed Damnation! and fell. -The stone gargoyle under the balustrade broke his fall. He clung to it with a frantic grip, and opened his mouth to utter a cry for help; but at the same moment the formidable and avenging face of Quasimodo rose over the edge of the balustrade above himand he was silent. -Beneath him was the abyss, a fall of full two hundred feet and the pavement. In this dreadful situation the Archdeacon said not a word, breathed not a groan. He writhed upon the gargoyle, making incredible efforts to climb up it; but his hand slipped on the smooth granite, his feet scraped the blackened wall without gaining a foothold. Those who have ascended the towers of Note-Dame know that the stone-work swells out immediately beneath the balustrade. It was on the retreating curve of this ridge that the wretched priest was exhausting his efforts. It was not even with a perpendicular wall that he was contending, but with one that sloped away under him. -Quasimodo had only to stretch out a hand to draw him out of the gulf, but he never so much as looked at him. He was absorbed in watching the Grve; watching the gibbet; watching the gipsy girl. -The hunchback was leaning, with his elbows on the balustrade, in the very place where the Archdeacon had been a moment before; and there, keeping his eye fixed on the only object that existed for him at that moment, he stood mute and motionless as a statue, save for the long stream of tears that flowed from that eye which, until then, had never shed but one. -Meanwhile the Archdeacon panted and struggled, drops of agony pouring from his bald forehead, his nails torn and bleeding on the stones, his knees grazed against the wall. He heard his soutane, which had caught on a projection of the stone rain-pipe, tear away at each movement he made. To complete his misfortune, the gutter itself ended in a leaden pipe which he could feel slowly bending under the weight of his body, and the wretched man told himself that when his hands should be worn out with fatigue, when his cassock should be rent asunder, when that leaden pipe should be completely bent, he must of necessity fall, and terror gripped his vitals. Once or twice he had wildly looked down upon a sort of narrow ledge formed, some ten feet below him, by the projection of the sculpture, and he implored Heaven, from the bottom of his agonized soul, to be allowed to spend the remainder of his life on that space of two feet square, though it were to last a hundred years. Once he ventured to look down into the Place, but when he lifted his head again his eyes were closed and his hair stood erect. -There was something appalling in the silence of these two men. While the Archdeacon hung in agony but a few feet below him, Quasimodo gazed upon the Place de Grve and wept. -The Archdeacon, finding that his struggles to raise himself only served to bend the one feeble point of support that remained to him, at length resolved to remain still. There he hung, clinging to the rain-pipe, scarcely drawing breath, with no other motion but the mechanical contractions of the body we feel in dreams when we imagine we are falling. His eyes were fixed and wide in a stare of pain and bewilderment. Little by little he felt himself going; his fingers slipped upon the stone; he was conscious more and more of the weakness of his arm and the weight of his body; the piece of lead strained ever farther downward. -Beneath himfrightful visionhe saw the sharp roof of Saint-Jean-le-Rond, like a card bent double. One by one he looked at the impassive sculptured figures round the tower, suspended, like himself, over the abyss, but without terror for themselves or pity for him. All about him was stonethe grinning monsters before his eyes; below, in the Place, the pavement; over his head, Quasimodo. -Down in the Parvis a group of worthy citizens were staring curiously upward, and wondering what madman it could be amusing himself after so strange a fashion. The priest could hear them say, for their voices rose clear and shrill in the quiet air: He will certainly break his neck! -Quasimodo was weeping. -At length the priest, foaming with impotent rage and terror, felt that all was unavailing, but gathered what strength still remained to him for one final effort. He drew himself up by the gutter, thrust himself out from the wall by both knees, dug his hands in a cleft of the stone-work, and managed to scramble up about one foot higher; but the force he was obliged to use made the leaden beak that supported him bend suddenly downward, and the strain rent his cassock through. Then, finding everything giving way under him, having only his benumbed and powerless hands by which to cling to anything, the wretched man closed his eyes, loosened his hold, and dropped. -Quasimodo watched him falling. A fall from such a height is rarely straight. The priest launched into space, fell at first head downward and his arms outstretched, then turned over on himself several times. The wind drove him against the roof of a house, where the unhappy man got his first crashing shock. He was not dead, however, and the hunchback saw him grasp at the gable to save himself; but the slope was too sheer, his strength was exhausted: he slid rapidly down the roof, like a loosened tile, and rebounded on to the pavement. There he lay motionless at last. -Quasimodo returned his gaze to the gipsy girl, whose body, dangling in its white robe from the gibbet, he beheld from afar quivering in the last agonies of death; then he let it drop once more on the Archdeacon, lying in a shapeless heap at the foot of the tower, and with a sigh that heaved his deep chest, he murmured: Oh! all that I have ever loved! -Chapter 3 - The Marriage of Ph?bus -Towards the evening of that day, when the bishops officers of justice came to remove the shattered remains of the Archdeacon from the Parvis, Quasimodo had disappeared. -This circumstance gave rise to many rumours. Nobody doubted, however, that the day had at length arrived when, according to the compact, Quasimodootherwise the devil was to carry off Claude Frollootherwise the sorcerer. It was presumed that he had broken the body in order to extract the soul, as a monkey cracks a nut-shell to get at the kernel. -It was for this reason the Archdeacon was denied Christian burial. -Louis XI died the following year, in August, 1483. -As for Pierre Gringoire, he not only succeeded in saving the goat, but gained considerable success as a writer of tragedies. It appears that after dabbling in astronomy, philosophy, architecture, hermeticsin short, every variety of crazehe returned to tragedy, which is the craziest of the lot. This is what he called coming to a tragic end. Touching his dramatic triumphs, we read in the royal privy accounts for 1483: -To Jehan Marchand and Pierre Gringoire, carpenter and composer, for making and composing the Mystery performed at the Chatelet of Paris on the day of the entry of Monsieur the Legate; for duly ordering the characters, with properties and habiliments proper to the said Mystery, as likewise for constructing the wooden stages necessary for the same: one hundred livres. -Ph?bus de Chateaupers also came to a tragic endhe married. -Chapter 4 - The Marriage of Quasimodo -We have already said that Quasimodo disappeared from Notre-Dame on the day of the death of the gipsy girl and the Archdeacon. He was never seen again, nor was it known what became of him. -In the night following the execution of Esmeralda, the hangmans assistants took down her body from the gibbet and carried it, according to custom, to the great charnel vault of Montfaucon. -Montfaucon, to use the words of Sauval, was the most ancient and the most superb gibbet in the kingdom. Between the faubourgs of the Temple and Saint-Martin, about a hundred and sixty toises from the wall of Paris and a few bow-shots from La Courtille, there stood on the highest point of a very slight eminence, but high enough to be visible for several leagues round, an edifice of peculiar form, much resembling a Celtic cromlech, and claiming like the cromlech its human sacrifices. -Let the reader imagine a huge oblong mass of masonry fifteen feet high, thirty feet wide, and forty feet long, on a plaster base, with a door, an external railing, and a platform; on this platform sixteen enormous pillars of rough hewn stone, thirty feet high, ranged as a colonnade round three of the four sides of the immense block supporting them, and connected at the top by heavy beams, from which hung chains at regular intervals; at each of these chains, skeletons; close by, in the plain, a stone cross and two secondary gibbets, rising like shoots of the great central tree; in the sky, hovering over the whole, a perpetual crowd of carrion crows. There you have Montfaucon. -By the end of the fifteenth century, this formidable gibbet, which had stood since 1328, had fallen upon evil days. The beams were worm-eaten, the chains corroded with rust, the pillars green with mould, the blocks of hewn stone gaped away from one another, and grass was growing on the platform on which no human foot ever trod now. The structure showed a ghastly silhouette against the skyespecially at night, when the moonlight gleamed on whitened skulls, and the evening breeze, sweeping through the chains and skeletons, set them rattling in the gloom. The presence of this gibbet sufficed to cast a blight over every spot within the range of its accursed view. -The mass of masonry that formed the base of the repulsive edifice was hollow, and an immense cavern had been constructed in it, closed by an old battered iron grating, into which were thrown not only the human relics that fell from the chains of Montfaucon itself, but also the bodies of the victims of all the other permanent gibbets of Paris. To that deep charnel-house, where so many human remains and the memory of so many crimes have rotted and mingled together, many a great one of the earth, and many an innocent victim, have contributed their bones, from Enguerrand de Martigny, who inaugurated Montfaucon, and was one of the just, down to Admiral de Colignylikewise one of the justwho closed it. -As for Quasimodos mysterious disappearance, all that we have been able to ascertain on the subject is this: -About a year and a half or two years after the concluding events of this story, when search was being made in the pit of Montfaucon for the body of Olivier le Daim, who had been hanged two days before, and to whom Charles VIII granted the favour of being interred at Saint-Laurent in better company, there were found among these hideous carcases two skeletons, the one clasped in the arms of the other. One of these skeletons, which was that of a woman, had still about it some tattered remnants of a garment that had once been white, and about its neck was a string of beads together with a small silken bag ornamented with green glass, but open and empty. These objects had been of so little value that the executioner, doubtless, had scorned to take them. The other skeleton, which held this one in so close a clasp, was that of a man. It was observed that the spine was crooked, the skull compressed between the shoulder-blades, and that one leg was shorter than the other. There was no rupture of the vertebr? at the nape of the neck, from which it was evident that the man had not been hanged. He must, therefore, have come of himself and died there. -When they attempted to detach this skeleton from the one it was embracing, it fell to dust. - - - - diff --git a/quick-spark/src/main/resources/file/hadoop.dll b/quick-spark/src/main/resources/file/hadoop.dll deleted file mode 100644 index aee0551e..00000000 Binary files a/quick-spark/src/main/resources/file/hadoop.dll and /dev/null differ diff --git a/quick-spark/src/main/resources/file/hdfs.dll b/quick-spark/src/main/resources/file/hdfs.dll deleted file mode 100644 index 63f5da3d..00000000 Binary files a/quick-spark/src/main/resources/file/hdfs.dll and /dev/null differ diff --git a/quick-spark/src/main/resources/file/winutils.exe b/quick-spark/src/main/resources/file/winutils.exe deleted file mode 100644 index a84428b0..00000000 Binary files a/quick-spark/src/main/resources/file/winutils.exe and /dev/null differ diff --git a/quick-spark/src/main/resources/file/zlib1.dll b/quick-spark/src/main/resources/file/zlib1.dll deleted file mode 100644 index d2f4672a..00000000 Binary files a/quick-spark/src/main/resources/file/zlib1.dll and /dev/null differ diff --git a/quick-spark/src/main/resources/ham.txt b/quick-spark/src/main/resources/ham.txt deleted file mode 100644 index a002eb4a..00000000 --- a/quick-spark/src/main/resources/ham.txt +++ /dev/null @@ -1,7 +0,0 @@ -Dear Spark Learner, Thanks so much for attending the Spark Summit 2014! Check out videos of talks from the summit at ... -Hi Mom, Apologies for being late about emailing and forgetting to send you the package. I hope you and bro have been ... -Wow, hey Fred, just heard about the Spark petabyte sort. I think we need to take time to try it out immediately ... -Hi Spark user list, This is my first question to this list, so thanks in advance for your help! I tried running ... -Thanks Tom for your email. I need to refer you to Alice for this one. I haven't yet figured out that part either ... -Good job yesterday! I was attending your talk, and really enjoyed it. I want to try out GraphX ... -Summit demo got whoops from audience! Had to let you know. --Joe diff --git a/quick-spark/src/main/resources/log.txt b/quick-spark/src/main/resources/log.txt deleted file mode 100644 index 42098a00..00000000 --- a/quick-spark/src/main/resources/log.txt +++ /dev/null @@ -1,6 +0,0 @@ -121.205.198.92 - - [21/Feb/2014:00:00:07 +0800] "GET /archives/417.html HTTP/1.1" 200 11465 "http://shiyanjun.cn/archives/417.html/" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0" -121.205.198.92 - - [21/Feb/2014:00:00:11 +0800] "POST /wp-comments-post.php HTTP/1.1" 302 26 "http://shiyanjun.cn/archives/417.html/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0" -121.205.198.92 - - [21/Feb/2014:00:00:12 +0800] "GET /archives/417.html/ HTTP/1.1" 301 26 "http://shiyanjun.cn/archives/417.html/" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0" -121.205.198.92 - - [21/Feb/2014:00:00:12 +0800] "GET /archives/417.html HTTP/1.1" 200 11465 "http://shiyanjun.cn/archives/417.html" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0" -121.205.241.229 - - [21/Feb/2014:00:00:13 +0800] "GET /archives/526.html HTTP/1.1" 200 12080 "http://shiyanjun.cn/archives/526.html/" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0" -121.205.241.229 - - [21/Feb/2014:00:00:15 +0800] "POST /wp-comments-post.php HTTP/1.1" 302 26 "http://shiyanjun.cn/archives/526.html/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0" \ No newline at end of file diff --git a/quick-spark/src/main/resources/log4j.properties b/quick-spark/src/main/resources/log4j.properties deleted file mode 100644 index 5bdffb98..00000000 --- a/quick-spark/src/main/resources/log4j.properties +++ /dev/null @@ -1,31 +0,0 @@ -# LOG4J -log4j.rootCategory=info, stdout, file, errorfile -log4j.category.com.didispace=DEBUG, didifile -log4j.logger.error=errorfile - -# ̨ -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n - -# root־ -log4j.appender.file=org.apache.log4j.DailyRollingFileAppender -log4j.appender.file.file=logs/all.log -log4j.appender.file.DatePattern='.'yyyy-MM-dd -log4j.appender.file.layout=org.apache.log4j.PatternLayout -log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n - -# error־ -log4j.appender.errorfile=org.apache.log4j.DailyRollingFileAppender -log4j.appender.errorfile.file=logs/error.log -log4j.appender.errorfile.DatePattern='.'yyyy-MM-dd -log4j.appender.errorfile.Threshold = ERROR -log4j.appender.errorfile.layout=org.apache.log4j.PatternLayout -log4j.appender.errorfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n - -# com.didispaceµ־ -log4j.appender.didifile=org.apache.log4j.DailyRollingFileAppender -log4j.appender.didifile.file=logs/my.log -log4j.appender.didifile.DatePattern='.'yyyy-MM-dd -log4j.appender.didifile.layout=org.apache.log4j.PatternLayout -log4j.appender.didifile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L ---- %m%n diff --git a/quick-spark/src/main/resources/spam.txt b/quick-spark/src/main/resources/spam.txt deleted file mode 100644 index 55641dd9..00000000 --- a/quick-spark/src/main/resources/spam.txt +++ /dev/null @@ -1,5 +0,0 @@ -Dear sir, I am a Prince in a far kingdom you have not heard of. I want to send you money via wire transfer so please ... -Get Viagra real cheap! Send money right away to ... -Oh my gosh you can be really strong too with these drugs found in the rainforest. Get them cheap right now ... -YOUR COMPUTER HAS BEEN INFECTED! YOU MUST RESET YOUR PASSWORD. Reply to this email with your password and SSN ... -THIS IS NOT A SCAM! Send money and get access to awesome stuff really cheap and never have to ... diff --git a/quick-spring-shiro/README.md b/quick-spring-shiro/README.md new file mode 100644 index 00000000..56b8d766 --- /dev/null +++ b/quick-spring-shiro/README.md @@ -0,0 +1,30 @@ +### 参考链接 +- https://zhuanlan.zhihu.com/p/140454269 + + +1、身份认证和鉴权; + +认证成功后的信息保存 + +2、访问之前的拦截; + +3、认证之后的拦截; + +4、支持 oauth2、session、jwt + +5、session 存在 + +### 问题 +1、 提示如下 +```text +Parameter 0 of method authorizationAttributeSourceAdvisor in org.apache.shiro.spring.boot.autoconfigure.ShiroAnnotationProcessorAutoConfiguration required a bean named 'authenticator' that could not be found. +Action: +Consider defining a bean named 'authenticator' in your configuration. +``` + +排查`SecurityManager`实例话的类型是否为`SessionsSecurityManager` + +2、Request method 'GET' not supported + +shiroFilterFactoryBean实例化是,设置的setLogin method为post,其他无权限访问的get接口会重定向到setlogin +就会出现此错误; diff --git a/quick-spring-shiro/pom.xml b/quick-spring-shiro/pom.xml new file mode 100644 index 00000000..51494164 --- /dev/null +++ b/quick-spring-shiro/pom.xml @@ -0,0 +1,62 @@ + + + quick-spring-shiro + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + true + + + + + org.apache.shiro + shiro-spring-boot-web-starter + + + com.alibaba + fastjson + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/ShiroApplication.java b/quick-spring-shiro/src/main/java/com/shiro/ShiroApplication.java new file mode 100644 index 00000000..43549b80 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/ShiroApplication.java @@ -0,0 +1,17 @@ +package com.shiro; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2019-12-01 19:54 + * + */ +@SpringBootApplication +public class ShiroApplication { + public static void main(String[] args) { + SpringApplication.run(ShiroApplication.class); + } +} diff --git a/quick-spring-shiro/src/main/java/com/shiro/config/ShiroConfig.java b/quick-spring-shiro/src/main/java/com/shiro/config/ShiroConfig.java new file mode 100644 index 00000000..7cc5a325 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/config/ShiroConfig.java @@ -0,0 +1,73 @@ +package com.shiro.config; + +import com.shiro.shiro.filter.CustomFormAuthenticationFilter; +import com.shiro.shiro.realm.MyShiroRealm; +import org.apache.shiro.authc.credential.HashedCredentialsMatcher; +import org.apache.shiro.crypto.hash.Md5Hash; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.mgt.SessionsSecurityManager; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author wangxc + * @date: 2019-12-01 22:30 + */ +@Configuration +public class ShiroConfig { + + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean() { + ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); + factoryBean.setLoginUrl("/user/loginUrl"); // 需要登陆时重定向的一个页面地址 + factoryBean.setSuccessUrl("/user/success"); + factoryBean.setUnauthorizedUrl("/unauthorized"); + factoryBean.setSecurityManager(manager()); + + // org.apache.shiro.web.filter.mgt.DefaultFilter + HashMap filterChainDefinitionMap = new LinkedHashMap(); + filterChainDefinitionMap.put("/user/login", "anon"); + filterChainDefinitionMap.put("/user/success", "authc"); + filterChainDefinitionMap.put("/unauthorized","anon" ); + filterChainDefinitionMap.put("/**", "authc"); + +// Map filters = new HashMap<>(); +// filters.put("customFormAuthenticationFilter", new CustomFormAuthenticationFilter()); +// factoryBean.setFilters(filters); + + factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + return factoryBean; + } + + @Bean(name = "manager") + public SessionsSecurityManager manager() { + DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); + defaultSecurityManager.setRealm(realm());// 单领域认证 + return defaultSecurityManager; + + } + + //注入自定义的realm,告诉shiro如何获取用户信息来做登录或权限控制 + @Bean + public MyShiroRealm realm() { + MyShiroRealm myShiroRealm = new MyShiroRealm(); + HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); + hashedCredentialsMatcher.setHashIterations(100); + hashedCredentialsMatcher.setHashAlgorithmName(Md5Hash.ALGORITHM_NAME); + myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher); + myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher); + return myShiroRealm; + } + + + + + +} diff --git a/quick-spring-shiro/src/main/java/com/shiro/config/WebConfig.java b/quick-spring-shiro/src/main/java/com/shiro/config/WebConfig.java new file mode 100644 index 00000000..31bb7f76 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/config/WebConfig.java @@ -0,0 +1,51 @@ +package com.shiro.config; + +import com.alibaba.fastjson.support.config.FastJsonConfig; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import com.shiro.shiro.interceptor.AccessInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +import static com.alibaba.fastjson.serializer.SerializerFeature.*; + +/** + * + * @author wangxc + * @date: 2019-12-11 22:22 + * + */ +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void configureMessageConverters(List> converters) { + FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); + //序列化配置 + FastJsonConfig config = new FastJsonConfig(); + config.setSerializerFeatures( + QuoteFieldNames,// 输出key时是否使用双引号 + WriteMapNullValue,// 是否输出值为null的字段 + WriteNullNumberAsZero,//数值字段如果为null,输出为0,而非null + WriteNullListAsEmpty,//List字段如果为null,输出为[],而非null + WriteNullStringAsEmpty,//字符类型字段如果为null,输出为"",而非null + //WriteNullBooleanAsFalse,//Boolean字段如果为null,输出为false,而非null + //WriteNullStringAsEmpty,// null String不输出 + //WriteMapNullValue,//null String也要输出 + //WriteDateUseDateFormat,//Date的日期转换器 + DisableCircularReferenceDetect//禁止循环引用 + ); + converter.setFastJsonConfig(config); + converters.add(converter); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new AccessInterceptor()).addPathPatterns("/**"); + WebMvcConfigurer.super.addInterceptors(registry); + } +} diff --git a/quick-spring-shiro/src/main/java/com/shiro/constant/Codes.java b/quick-spring-shiro/src/main/java/com/shiro/constant/Codes.java new file mode 100644 index 00000000..46c7a47c --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/constant/Codes.java @@ -0,0 +1,17 @@ +package com.shiro.constant; + +public interface Codes { + + /** 未登录 */ + int UNAUTHEN = 4401; + + /** 未授权,拒绝访问 */ + int UNAUTHZ = 4403; + + /** shiro相关的错误 */ + int SHIRO_ERR = 4444; + + /** 服务端异常 */ + int SERVER_ERR = 5500; + +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/controller/BusController.java b/quick-spring-shiro/src/main/java/com/shiro/controller/BusController.java new file mode 100644 index 00000000..0b35d4b7 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/controller/BusController.java @@ -0,0 +1,17 @@ +package com.shiro.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/biz") +@RestController +@Slf4j +public class BusController { + + @GetMapping("/info") + public String info() { + return "auth success info"; + } +} diff --git a/quick-spring-shiro/src/main/java/com/shiro/controller/GlobalExceptionHandler.java b/quick-spring-shiro/src/main/java/com/shiro/controller/GlobalExceptionHandler.java new file mode 100644 index 00000000..eb8acff2 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/controller/GlobalExceptionHandler.java @@ -0,0 +1,40 @@ +package com.shiro.controller; + +import com.shiro.constant.Codes; +import com.shiro.vo.Json; +import org.apache.shiro.ShiroException; +import org.apache.shiro.authz.UnauthenticatedException; +import org.apache.shiro.authz.UnauthorizedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler(ShiroException.class) + @ResponseBody + public Json handleShiroException(ShiroException e) { + String eName = e.getClass().getSimpleName(); + log.error("shiro执行出错:{}", eName); + return new Json(eName, false, Codes.SHIRO_ERR, "鉴权或授权过程出错", null); + } + + @ExceptionHandler(UnauthenticatedException.class) + @ResponseBody + public Json page401() { + return new Json("401", false, Codes.UNAUTHEN, "用户未登录", null); + } + + @ExceptionHandler(UnauthorizedException.class) + @ResponseBody + public Json page403() { + return new Json("403", false, Codes.UNAUTHZ, "用户没有访问权限", null); + } + +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/controller/UserController.java b/quick-spring-shiro/src/main/java/com/shiro/controller/UserController.java new file mode 100644 index 00000000..800bd0f6 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/controller/UserController.java @@ -0,0 +1,84 @@ +package com.shiro.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.shiro.entity.User; +import com.shiro.vo.Json; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.*; +import org.apache.shiro.subject.Subject; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +/** + * @author wangxc + * @date: 2019-12-11 22:40 + */ +@RequestMapping("/user") +@RestController +@Slf4j +public class UserController { + + + @GetMapping("/unauthorized") + public String unauthorized() { + return "unauthorized"; + } + + @GetMapping("/loginUrl") + public String loginPage() { + + return "需要登陆,请使用登陆接口/user/login"; + } + + @RequestMapping("/login") + public Json login(@RequestBody String body) { + String oper = "user login"; + log.info("{}, body: {}", oper, body); + JSONObject json = JSON.parseObject(body); + String uname = json.getString("uname"); + String pwd = json.getString("pwd"); + if (StringUtils.isEmpty(uname)) { + return Json.fail(oper, "用户名不能为空"); + } + if (StringUtils.isEmpty(pwd)) { + return Json.fail(oper, "密码不能为空"); + } + Subject currentUser = SecurityUtils.getSubject(); + try { + //登录 + currentUser.login(new UsernamePasswordToken(uname, pwd)); + //从session取出用户信息 + User user = (User) currentUser.getPrincipal(); + if (user == null) + throw new AuthenticationException(); + //返回登录用户的信息给前台,含用户的所有角色和权限 + return Json.succ(oper).data("uid", user.getUid()).data("nick", user.getNick()) + .data("roles", user.getRoles()).data("perms", user.getPerms()); + + } catch (UnknownAccountException uae) { + log.warn("用户帐号不正确"); + return Json.fail(oper, "用户帐号或密码不正确"); + + } catch (IncorrectCredentialsException ice) { + log.warn("用户密码不正确"); + return Json.fail(oper, "用户帐号或密码不正确"); + + } catch (LockedAccountException lae) { + log.warn("用户帐号被锁定"); + return Json.fail(oper, "用户帐号被锁定不可用"); + + } catch (AuthenticationException ae) { + log.warn("登录出错"); + return Json.fail(oper, "登录失败:" + ae.getMessage()); + } + } + + + @RequestMapping("/success") + public String methodName() { + + return "登陆成功"; + } +} diff --git a/quick-spring-shiro/src/main/java/com/shiro/entity/Perm.java b/quick-spring-shiro/src/main/java/com/shiro/entity/Perm.java new file mode 100644 index 00000000..e1cc1252 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/entity/Perm.java @@ -0,0 +1,86 @@ +package com.shiro.entity; + +import java.util.Date; + +public class Perm { + + /** + * 权限类型:菜单 + */ + public static int PTYPE_MENU = 1; + /** + * 权限类型:按钮 + */ + public static int PTYPE_BUTTON = 2; + + private Long pid; // 权限id + private String pname; // 权限名称 + private Integer ptype; // 权限类型:1.菜单;2.按钮 + private String pval; // 权限值,shiro的权限控制表达式 + private Date created; // 创建时间 + private Date updated; // 修改时间 + + public static int getPtypeMenu() { + return PTYPE_MENU; + } + + public static void setPtypeMenu(int ptypeMenu) { + PTYPE_MENU = ptypeMenu; + } + + public static int getPtypeButton() { + return PTYPE_BUTTON; + } + + public static void setPtypeButton(int ptypeButton) { + PTYPE_BUTTON = ptypeButton; + } + + public Long getPid() { + return pid; + } + + public void setPid(Long pid) { + this.pid = pid; + } + + public String getPname() { + return pname; + } + + public void setPname(String pname) { + this.pname = pname; + } + + public Integer getPtype() { + return ptype; + } + + public void setPtype(Integer ptype) { + this.ptype = ptype; + } + + public String getPval() { + return pval; + } + + public void setPval(String pval) { + this.pval = pval; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/entity/Role.java b/quick-spring-shiro/src/main/java/com/shiro/entity/Role.java new file mode 100644 index 00000000..582888cb --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/entity/Role.java @@ -0,0 +1,62 @@ +package com.shiro.entity; + +import java.util.Date; + +public class Role { + + private Long rid; // 角色id + private String rname; // 角色名,用于显示 + private String rdesc; // 角色描述 + private String rval; // 角色值,用于权限判断 + private Date created; // 创建时间 + private Date updated; // 修改时间 + + + public Long getRid() { + return rid; + } + + public void setRid(Long rid) { + this.rid = rid; + } + + public String getRname() { + return rname; + } + + public void setRname(String rname) { + this.rname = rname; + } + + public String getRdesc() { + return rdesc; + } + + public void setRdesc(String rdesc) { + this.rdesc = rdesc; + } + + public String getRval() { + return rval; + } + + public void setRval(String rval) { + this.rval = rval; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/entity/RolePerm.java b/quick-spring-shiro/src/main/java/com/shiro/entity/RolePerm.java new file mode 100644 index 00000000..6a3e6d4b --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/entity/RolePerm.java @@ -0,0 +1,25 @@ +package com.shiro.entity; + +import java.io.Serializable; + +public class RolePerm implements Serializable { + + private Long roleId; + private Long permId; + + public Long getRoleId() { + return roleId; + } + + public void setRoleId(Long roleId) { + this.roleId = roleId; + } + + public Long getPermId() { + return permId; + } + + public void setPermId(Long permId) { + this.permId = permId; + } +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/entity/User.java b/quick-spring-shiro/src/main/java/com/shiro/entity/User.java new file mode 100644 index 00000000..1477dbf8 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/entity/User.java @@ -0,0 +1,90 @@ +package com.shiro.entity; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +public class User { + + private Long uid; // 用户id + private String uname; // 登录名,不可改 + private String nick; // 用户昵称,可改 + private String pwd; // 已加密的登录密码 + private String salt; // 加密盐值 + private Date created; // 创建时间 + private Date updated; // 修改时间 + private Set roles = new HashSet<>(); //用户所有角色值,用于shiro做角色权限的判断 + private Set perms = new HashSet<>(); //用户所有权限值,用于shiro做资源权限的判断 + + public Long getUid() { + return uid; + } + + public void setUid(Long uid) { + this.uid = uid; + } + + public String getUname() { + return uname; + } + + public void setUname(String uname) { + this.uname = uname; + } + + public String getNick() { + return nick; + } + + public void setNick(String nick) { + this.nick = nick; + } + + public String getPwd() { + return pwd; + } + + public void setPwd(String pwd) { + this.pwd = pwd; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public Set getPerms() { + return perms; + } + + public void setPerms(Set perms) { + this.perms = perms; + } +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/entity/UserRole.java b/quick-spring-shiro/src/main/java/com/shiro/entity/UserRole.java new file mode 100644 index 00000000..5d67ea69 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/entity/UserRole.java @@ -0,0 +1,25 @@ +package com.shiro.entity; + +import java.io.Serializable; + +public class UserRole implements Serializable { + + private Long userId; + private Long roleId; + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getRoleId() { + return roleId; + } + + public void setRoleId(Long roleId) { + this.roleId = roleId; + } +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/service/PermService.java b/quick-spring-shiro/src/main/java/com/shiro/service/PermService.java new file mode 100644 index 00000000..d90791be --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/service/PermService.java @@ -0,0 +1,33 @@ +package com.shiro.service; + +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.Set; + +@Service +public class PermService { + + /** + * 模拟根据用户id查询返回用户的所有权限,实际查询语句参考: + * SELECT p.pval FROM perm p, role_perm rp, user_role ur + * WHERE p.pid = rp.perm_id AND ur.role_id = rp.role_id + * AND ur.user_id = #{userId} + * @param uid + * @return + */ + public Set getPermsByUserId(Long uid) { + Set perms = new HashSet<>(); + //三种编程语言代表三种角色:js程序员、java程序员、c++程序员 + //js程序员的权限 + perms.add("html:edit"); + //c++程序员的权限 + perms.add("hardware:debug"); + //java程序员的权限 + perms.add("mvn:install"); + perms.add("mvn:clean"); + perms.add("mvn:test"); + return perms; + } + +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/service/RoleService.java b/quick-spring-shiro/src/main/java/com/shiro/service/RoleService.java new file mode 100644 index 00000000..ab6d6200 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/service/RoleService.java @@ -0,0 +1,27 @@ +package com.shiro.service; + +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.Set; + +@Service +public class RoleService { + + /** + * 模拟根据用户id查询返回用户的所有角色,实际查询语句参考: + * SELECT r.rval FROM role r, user_role ur + * WHERE r.rid = ur.role_id AND ur.user_id = #{userId} + * @param uid + * @return + */ + public Set getRolesByUserId(Long uid) { + Set roles = new HashSet<>(); + //三种编程语言代表三种角色:js程序员、java程序员、c++程序员 + roles.add("js"); + roles.add("java"); + roles.add("cpp"); + return roles; + } + +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/service/UserService.java b/quick-spring-shiro/src/main/java/com/shiro/service/UserService.java new file mode 100644 index 00000000..99d4e0e8 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/service/UserService.java @@ -0,0 +1,33 @@ +package com.shiro.service; + +import com.shiro.entity.User; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.Random; + +/** + * + * @author wangxc + * @date: 2019-12-11 22:06 + * + */ +@Service +public class UserService { + /** + * 模拟查询返回用户信息 + * @param uname + * @return + */ + public User findUserByName(String uname) { + User user = new User(); + user.setUname(uname); + user.setNick(uname + "NICK"); + user.setPwd("e90d70cde308af541eee5a8ef3c7f08e");//密码明文是123456 md5 打散100次 +// user.setPwd("J/ms7qTJtqmysekuY8/v1TAS+VKqXdH5sB7ulXZOWho=");//密码明文是123456 + user.setSalt("wxKYXuTPST5SG0jMQzVPsg==");//加密密码的盐值 + user.setUid(new Random().nextLong());//随机分配一个id + user.setCreated(new Date()); + return user; + } +} diff --git a/quick-spring-shiro/src/main/java/com/shiro/shiro/filter/CustomFormAuthenticationFilter.java b/quick-spring-shiro/src/main/java/com/shiro/shiro/filter/CustomFormAuthenticationFilter.java new file mode 100644 index 00000000..58d900d2 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/shiro/filter/CustomFormAuthenticationFilter.java @@ -0,0 +1,21 @@ +package com.shiro.shiro.filter; + +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +@Slf4j +@Component(value = "customFormAuthenticationFilter") +public class CustomFormAuthenticationFilter extends FormAuthenticationFilter { + + @Override + protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { + log.info("login success"); + return super.onLoginSuccess(token, subject, request, response); + } +} diff --git a/quick-spring-shiro/src/main/java/com/shiro/shiro/interceptor/AccessInterceptor.java b/quick-spring-shiro/src/main/java/com/shiro/shiro/interceptor/AccessInterceptor.java new file mode 100644 index 00000000..9bca1561 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/shiro/interceptor/AccessInterceptor.java @@ -0,0 +1,56 @@ +package com.shiro.shiro.interceptor; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.support.RequestContextUtils; +import org.springframework.web.util.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Enumeration; + +@Slf4j +public class AccessInterceptor implements HandlerInterceptor { + + /** + * 请求拦截器 + * preHandle:在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理; + * postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView (这个博主就基本不怎么用了); + * afterCompletion:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面); + * 验证用户登录权限 + * @param request current HTTP request + * @param response current HTTP response + * @param handler chosen handler to execute, for type and/or instance evaluation + * @return + * @throws Exception + */ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + log.info("AccessInterceptor preHandle"); + Enumeration headerNames = request.getHeaderNames(); + while(headerNames.hasMoreElements()) { + String name = (String)headerNames.nextElement(); + System.out.println(name+": "+ request.getHeader(name)); + } + + + + + + return HandlerInterceptor.super.preHandle(request, response, handler); + } + + +// @Override +// public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { +// log.info("AccessInterceptor postHandle"); +// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); +// } +// +// @Override +// public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { +// log.info("AccessInterceptor afterCompletion"); +// HandlerInterceptor.super.afterCompletion(request, response, handler, ex); +// } +} diff --git a/quick-spring-shiro/src/main/java/com/shiro/shiro/realm/MyShiroRealm.java b/quick-spring-shiro/src/main/java/com/shiro/shiro/realm/MyShiroRealm.java new file mode 100644 index 00000000..492149d5 --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/shiro/realm/MyShiroRealm.java @@ -0,0 +1,105 @@ +package com.shiro.shiro.realm; + +import com.shiro.entity.User; +import com.shiro.service.PermService; +import com.shiro.service.RoleService; +import com.shiro.service.UserService; +import org.apache.shiro.authc.*; +import org.apache.shiro.authc.credential.HashedCredentialsMatcher; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.crypto.hash.Sha256Hash; +import org.apache.shiro.crypto.hash.SimpleHash; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.util.ByteSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; + +import java.util.Objects; +import java.util.Set; + +/** + * + * @author wangxc + * @date: 2019-12-01 20:53 + * + */ +public class MyShiroRealm extends AuthorizingRealm { + + @Autowired + private UserService userService; + @Autowired + private RoleService roleService; + @Autowired + private PermService permService; + + + //告诉shiro如何根据获取到的用户信息中的密码和盐值来校验密码 + // 抽取到配置类中设置 +// { +// //设置用于匹配密码的CredentialsMatcher +// HashedCredentialsMatcher hashMatcher = new HashedCredentialsMatcher(); +// hashMatcher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME); +// hashMatcher.setStoredCredentialsHexEncoded(false); +// hashMatcher.setHashIterations(1024); +// this.setCredentialsMatcher(hashMatcher); +// } + + //定义如何获取用户的角色和权限的逻辑,给shiro做权限判断 + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + if (principals == null) { + throw new AuthorizationException("PrincipalCollection method argument cannot be null."); + } + User user = (User) getAvailablePrincipal(principals); + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + System.out.println("获取角色信息:" + user.getRoles()); + System.out.println("获取权限信息:" + user.getPerms()); + info.setRoles(user.getRoles()); + info.setStringPermissions(user.getPerms()); + return info; + } + + //定义如何获取用户信息的业务逻辑,给shiro做登录 + + /** + * 可在此逻辑下增加保存用户信息到session或缓存中 + * @param token the authentication token containing the user's principal and credentials. + * @return + * @throws AuthenticationException + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; + String username = usernamePasswordToken.getUsername(); + if (StringUtils.isEmpty(username)) { + throw new AccountException("Null usernames are not allowed by this realm."); + } + User userByName = userService.findUserByName(username); + if (Objects.isNull(userByName)) { + throw new UnknownAccountException("No account found for admin [" + username + "]"); + } + //查询用户的角色和权限存到SimpleAuthenticationInfo中,这样在其它地方 + //SecurityUtils.getSubject().getPrincipal()就能拿出用户的所有信息,包括角色和权限 + Set roles = roleService.getRolesByUserId(userByName.getUid()); + Set perms = permService.getPermsByUserId(userByName.getUid()); + userByName.getRoles().addAll(roles); + userByName.getPerms().addAll(perms); + SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userByName, userByName.getPwd(), getName()); + if (userByName.getSalt() != null) { + info.setCredentialsSalt(ByteSource.Util.bytes(userByName.getSalt())); + } + return info; + } + + public static void main(String[] args) { + String hashAlgorithmName = "MD5"; + String credentials = "123456"; + Object salt = "wxKYXuTPST5SG0jMQzVPsg=="; + int hashIterations = 100; + SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); + System.out.println(simpleHash); + } +} diff --git a/quick-spring-shiro/src/main/java/com/shiro/utils/CodeGenerator.java b/quick-spring-shiro/src/main/java/com/shiro/utils/CodeGenerator.java new file mode 100644 index 00000000..abaccb0e --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/utils/CodeGenerator.java @@ -0,0 +1,125 @@ +//package com.shiro.utils; +// +//import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; +//import com.baomidou.mybatisplus.core.toolkit.StringPool; +//import com.baomidou.mybatisplus.core.toolkit.StringUtils; +//import com.baomidou.mybatisplus.generator.AutoGenerator; +//import com.baomidou.mybatisplus.generator.InjectionConfig; +//import com.baomidou.mybatisplus.generator.config.*; +//import com.baomidou.mybatisplus.generator.config.po.TableInfo; +//import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +//import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; +// +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Scanner; +// +//public class CodeGenerator { +// +// /** +// *

+// * 读取控制台内容 +// *

+// */ +// public static String scanner(String tip) { +// Scanner scanner = new Scanner(System.in); +// StringBuilder help = new StringBuilder(); +// help.append("请输入" + tip + ":"); +// System.out.println(help.toString()); +// if (scanner.hasNext()) { +// String ipt = scanner.next(); +// if (StringUtils.isNotEmpty(ipt)) { +// return ipt; +// } +// } +// throw new MybatisPlusException("请输入正确的" + tip + "!"); +// } +// +// public static void main(String[] args) { +// // 代码生成器 +// AutoGenerator mpg = new AutoGenerator(); +// // 全局配置 +// GlobalConfig gc = new GlobalConfig(); +// String projectPath = System.getProperty("user.dir"); +// gc.setOutputDir(projectPath + "/src/main/java"); +// gc.setAuthor("jobob"); +// gc.setOpen(false); +// // gc.setSwagger2(true); 实体属性 Swagger2 注解 +// mpg.setGlobalConfig(gc); +// // 数据源配置 +// DataSourceConfig dsc = new DataSourceConfig(); +// dsc.setUrl("jdbc:mysql://localhost:3308/shiro?useUnicode=true&useSSL=false&characterEncoding=utf8"); +// // dsc.setSchemaName("public"); +// dsc.setDriverName("com.mysql.jdbc.Driver"); +// dsc.setUsername("root"); +// dsc.setPassword("123456"); +// mpg.setDataSource(dsc); +// // 包配置 +// PackageConfig pc = new PackageConfig(); +// pc.setModuleName(scanner("模块名")); +// pc.setParent("com.shiro"); +// mpg.setPackageInfo(pc); +// // 自定义配置 +// InjectionConfig cfg = new InjectionConfig() { +// @Override +// public void initMap() { +// // to do nothing +// } +// }; +// // 如果模板引擎是 freemarker +// String templatePath = "/templates/mapper.xml.ftl"; +// // 如果模板引擎是 velocity +// // String templatePath = "/templates/mapper.xml.vm"; +// // 自定义输出配置 +// List focList = new ArrayList<>(); +// // 自定义配置会被优先输出 +// focList.add(new FileOutConfig(templatePath) { +// +// @Override +// public String outputFile(TableInfo tableInfo) { +// // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! +// return projectPath + "/src/main/resources/mapper/" + pc.getModuleName() + "/" + tableInfo +// .getEntityName() + "Mapper" + StringPool.DOT_XML; +// } +// }); +// /* +// cfg.setFileCreate(new IFileCreate() { +// @Override +// public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { +// // 判断自定义文件夹是否需要创建 +// checkDir("调用默认方法创建的目录"); +// return false; +// } +// }); +// */ +// cfg.setFileOutConfigList(focList); +// mpg.setCfg(cfg); +// // 配置模板 +// TemplateConfig templateConfig = new TemplateConfig(); +// // 配置自定义输出模板 +// //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 +// // templateConfig.setEntity("templates/entity2.java"); +// // templateConfig.setService(); +// // templateConfig.setController(); +// templateConfig.setXml(null); +// mpg.setTemplate(templateConfig); +// // 策略配置 +// StrategyConfig strategy = new StrategyConfig(); +// strategy.setNaming(NamingStrategy.underline_to_camel); +// strategy.setColumnNaming(NamingStrategy.underline_to_camel); +//// strategy.setSuperEntityClass("com.baomidou.ant.common.BaseEntity"); +// strategy.setEntityLombokModel(true); +// strategy.setRestControllerStyle(true); +// // 公共父类 +//// strategy.setSuperControllerClass("com.baomidou.ant.common.BaseController"); +// // 写于父类中的公共字段 +// strategy.setSuperEntityColumns("id"); +// strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); +// strategy.setControllerMappingHyphenStyle(true); +// strategy.setTablePrefix(pc.getModuleName() + "_"); +// mpg.setStrategy(strategy); +// mpg.setTemplateEngine(new FreemarkerTemplateEngine()); +// mpg.execute(); +// } +// +//} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/java/com/shiro/vo/Json.java b/quick-spring-shiro/src/main/java/com/shiro/vo/Json.java new file mode 100644 index 00000000..14691ddd --- /dev/null +++ b/quick-spring-shiro/src/main/java/com/shiro/vo/Json.java @@ -0,0 +1,157 @@ +package com.shiro.vo; + +import java.util.HashMap; + +public class Json extends HashMap { + /////////////////////// 默认的键 /////////////////////// + + public static final String KEY_OPER = "oper"; + public static final String KEY_SUCC = "succ"; + public static final String KEY_CODE = "code"; + public static final String KEY_MSG = "msg"; + public static final String KEY_DATA = "data"; + /////////////////////// 默认的值 /////////////////////// + + public static final String DEFAULT_OPER_VAL = "default"; + public static final int DEFAULT_SUCC_CODE = 1; + public static final int DEFAULT_FAIL_CODE = -1; + public static final String DEFAULT_SUCC_MSG = "ok"; + public static final String DEFAULT_FAIL_MSG = "fail"; + /////////////////////// 最通用的两个构造函数 /////////////////////// + + /** + * 无参构造:操作成功返回的响应信息 + */ + public Json() { + this.put(KEY_OPER, DEFAULT_OPER_VAL); + this.put(KEY_SUCC, true); + this.put(KEY_CODE, DEFAULT_SUCC_CODE); + this.put(KEY_MSG, DEFAULT_SUCC_MSG); + } + + /** + * 操作成功返回的响应信息 + */ + public Json(String oper) { + this.put(KEY_OPER, oper); + this.put(KEY_SUCC, true); + this.put(KEY_CODE, DEFAULT_SUCC_CODE); + this.put(KEY_MSG, DEFAULT_SUCC_MSG); + } + + /** + * 全参构造 + * @param operate + * @param success + * @param code + * @param msg + * @param data + */ + public Json(String operate, boolean success, int code, String msg, Object data) { + this.put(KEY_OPER, operate); + this.put(KEY_SUCC, success); + this.put(KEY_CODE, code); + this.put(KEY_MSG, msg); + if (data != null) { + this.put(KEY_DATA, data); + } + } + /////////////////////// 各种简便的静态工厂方法 /////////////////////// + ////// 操作成功的: + + public static Json succ() { + return new Json(); + } + + public static Json succ(String operate) { + return new Json(operate, true, DEFAULT_SUCC_CODE, DEFAULT_SUCC_MSG, null); + } + + public static Json succ(String operate, String message) { + return new Json(operate, true, DEFAULT_SUCC_CODE, message, null); + } + + public static Json succ(String operate, Object data) { + return new Json(operate, true, DEFAULT_SUCC_CODE, DEFAULT_SUCC_MSG, data); + } + + public static Json succ(String operate, String dataKey, Object dataVal) { + return new Json(operate, true, DEFAULT_SUCC_CODE, DEFAULT_SUCC_MSG, null).data(dataKey, dataVal); + } + ////// 操作失败的: + + public static Json fail() { + return new Json(DEFAULT_OPER_VAL, false, DEFAULT_FAIL_CODE, DEFAULT_FAIL_MSG, null); + } + + public static Json fail(String operate) { + return new Json(operate, false, DEFAULT_FAIL_CODE, DEFAULT_FAIL_MSG, null); + } + + public static Json fail(String operate, String message) { + return new Json(operate, false, DEFAULT_FAIL_CODE, message, null); + } + + public static Json fail(String operate, Object data) { + return new Json(operate, false, DEFAULT_FAIL_CODE, DEFAULT_FAIL_MSG, data); + } + + public static Json fail(String operate, String dataKey, Object dataVal) { + return new Json(operate, false, DEFAULT_FAIL_CODE, DEFAULT_FAIL_MSG, null).data(dataKey, dataVal); + } + ////// 操作动态判定成功或失败的: + + public static Json result(String operate, boolean success) { + return new Json(operate, success, (success ? DEFAULT_SUCC_CODE : DEFAULT_FAIL_CODE), + (success ? DEFAULT_SUCC_MSG : DEFAULT_FAIL_MSG), null); + } + + public static Json result(String operate, boolean success, Object data) { + return new Json(operate, success, (success ? DEFAULT_SUCC_CODE : DEFAULT_FAIL_CODE), + (success ? DEFAULT_SUCC_MSG : DEFAULT_FAIL_MSG), data); + } + + public static Json result(String operate, boolean success, String dataKey, Object dataVal) { + return new Json(operate, success, (success ? DEFAULT_SUCC_CODE : DEFAULT_FAIL_CODE), + (success ? DEFAULT_SUCC_MSG : DEFAULT_FAIL_MSG), null).data(dataKey, dataVal); + } + /////////////////////// 各种链式调用方法 /////////////////////// + + /** 设置操作名称 */ + public Json oper(String operate) { + this.put(KEY_OPER, operate); + return this; + } + + /** 设置操作结果是否成功的标记 */ + public Json succ(boolean success) { + this.put(KEY_SUCC, success); + return this; + } + + /** 设置操作结果的代码 */ + public Json code(int code) { + this.put(KEY_CODE, code); + return this; + } + + /** 设置操作结果的信息 */ + public Json msg(String message) { + this.put(KEY_MSG, message); + return this; + } + + /** 设置操作返回的数据 */ + public Json data(Object dataVal) { + this.put(KEY_DATA, dataVal); + return this; + } + + /** 设置操作返回的数据,数据使用自定义的key存储 */ + public Json data(String dataKey, Object dataVal) { + this.put(dataKey, dataVal); + return this; + } + + +} \ No newline at end of file diff --git a/quick-spring-shiro/src/main/resources/.DS_Store b/quick-spring-shiro/src/main/resources/.DS_Store new file mode 100644 index 00000000..f54b2850 Binary files /dev/null and b/quick-spring-shiro/src/main/resources/.DS_Store differ diff --git a/quick-spring-shiro/src/main/resources/application.yml b/quick-spring-shiro/src/main/resources/application.yml new file mode 100644 index 00000000..a5cf4d58 --- /dev/null +++ b/quick-spring-shiro/src/main/resources/application.yml @@ -0,0 +1,6 @@ +server: + port: 8080 + +spring: + main: + allow-bean-definition-overriding: true \ No newline at end of file diff --git a/quick-sse/pom.xml b/quick-sse/pom.xml new file mode 100644 index 00000000..166c65a8 --- /dev/null +++ b/quick-sse/pom.xml @@ -0,0 +1,115 @@ + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + 4.0.0 + + quick-sse + jar + + quick-sse + http://maven.apache.org + + + UTF-8 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + cn.hutool + hutool-all + + + org.projectlombok + lombok + + + com.squareup.okhttp3 + okhttp + 4.9.3 + + + com.squareup.okhttp3 + okhttp-sse + 4.9.3 + + + junit + junit + 3.8.1 + test + + + + + + com.github.xiaoymin + knife4j-openapi3-spring-boot-starter + 4.0.0 + + + + org.springdoc + springdoc-openapi-common + + + org.springdoc + springdoc-openapi-webflux-core + + + org.springdoc + springdoc-openapi-webmvc-core + + + io.swagger.core.v3 + swagger-models + + + io.swagger.core.v3 + swagger-annotations + + + + + io.swagger.core.v3 + swagger-models + 2.2.7 + + + io.swagger.core.v3 + swagger-annotations + 2.2.7 + + + + org.springdoc + springdoc-openapi-common + 1.6.14 + + + org.springdoc + springdoc-openapi-webflux-core + 1.6.14 + + + org.springdoc + springdoc-openapi-webmvc-core + 1.6.14 + + + org.springdoc + springdoc-openapi-ui + 1.6.14 + + + diff --git a/quick-sse/src/main/java/com/quick/App.java b/quick-sse/src/main/java/com/quick/App.java new file mode 100644 index 00000000..f31c851b --- /dev/null +++ b/quick-sse/src/main/java/com/quick/App.java @@ -0,0 +1,15 @@ +package com.quick; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Hello world! + */ +@SpringBootApplication +public class App { + public static void main(String[] args) { + + SpringApplication.run(App.class); + } +} \ No newline at end of file diff --git a/quick-sse/src/main/java/com/quick/config/OkHttp3Config.java b/quick-sse/src/main/java/com/quick/config/OkHttp3Config.java new file mode 100644 index 00000000..374bed30 --- /dev/null +++ b/quick-sse/src/main/java/com/quick/config/OkHttp3Config.java @@ -0,0 +1,33 @@ +package com.quick.config; + +import okhttp3.OkHttpClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +@Configuration +public class OkHttp3Config { + + @Value("${proxy.hostname:127.0.0.1}") + private String proxyHostname; + + @Value("${proxy.port:1080}") + private int proxyPort; + + @Bean + public OkHttpClient okHttpClient() { + InetSocketAddress address = new InetSocketAddress(proxyHostname, proxyPort); + Proxy proxy = new Proxy(Proxy.Type.HTTP, address); + OkHttpClient.Builder client = new OkHttpClient.Builder().proxy(proxy); + /** + * 忽略ssl校验 + */ + client.sslSocketFactory(SSLSocketClient.getSSLSocketFactory()); + client.hostnameVerifier(SSLSocketClient.getHostnameVerifier()); + return client.build(); + } + +} \ No newline at end of file diff --git a/quick-sse/src/main/java/com/quick/config/SSLSocketClient.java b/quick-sse/src/main/java/com/quick/config/SSLSocketClient.java new file mode 100644 index 00000000..2879ed2f --- /dev/null +++ b/quick-sse/src/main/java/com/quick/config/SSLSocketClient.java @@ -0,0 +1,51 @@ +package com.quick.config; + +import javax.net.ssl.*; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +public class SSLSocketClient { + + //获取这个SSLSocketFactory + public static SSLSocketFactory getSSLSocketFactory() { + try { + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, getTrustManager(), new SecureRandom()); + return sslContext.getSocketFactory(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //获取TrustManager + private static TrustManager[] getTrustManager() { + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } + } + }; + return trustAllCerts; + } + + //获取HostnameVerifier + public static HostnameVerifier getHostnameVerifier() { + HostnameVerifier hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + }; + return hostnameVerifier; + } +} \ No newline at end of file diff --git a/quick-sse/src/main/java/com/quick/constant/NumConstant.java b/quick-sse/src/main/java/com/quick/constant/NumConstant.java new file mode 100644 index 00000000..c163d15a --- /dev/null +++ b/quick-sse/src/main/java/com/quick/constant/NumConstant.java @@ -0,0 +1,23 @@ +package com.quick.constant; + +@SuppressWarnings ("all") +public class NumConstant { + + public static final Integer NUM_0 = 0; + public static final Integer NUM_1 = 1; + public static final Integer NUM_2 = 2; + public static final Integer NUM_3 = 3; + public static final Integer NUM_4 = 4; + public static final Integer NUM_5 = 5; + public static final Integer NUM_6 = 6; + public static final Integer NUM_7 = 7; + public static final Integer NUM_8 = 8; + public static final Integer NUM_9 = 9; + public static final Integer NUM_10 = 10; + public static final Integer NUM_16 = 16; + public static final Integer NUM_32 = 32; + public static final Integer NUM_64 = 64; + public static final Integer NUM_128 = 128; + public static final Integer NUM_256 = 256; + public static final Integer NUM_512 = 512; +} diff --git a/quick-sse/src/main/java/com/quick/constant/RedisKeyConstant.java b/quick-sse/src/main/java/com/quick/constant/RedisKeyConstant.java new file mode 100644 index 00000000..8c9e0908 --- /dev/null +++ b/quick-sse/src/main/java/com/quick/constant/RedisKeyConstant.java @@ -0,0 +1,60 @@ +package com.quick.constant; + +public class RedisKeyConstant { + + /** + * redis key : 每日一题 + */ + public static final String DAILY_QUESTION_KEY = "question_daily:question"; + + /** + * redis key : 用户每日一题做题情况 + */ + public static final String USER_DAILY_QUESTION_KEY = "question_daily:user:%s"; + + + /** + * redis key : 点赞父评论 + */ + public static final String ROOT_COMMENT_LIKE_KEY = "like:root_comment"; + + /** + * redis key : 点赞子评论 + */ + public static final String SON_COMMENT_LIKE_KEY = "like:son_comment"; + + /** + * redis key : 点赞题解 + */ + public static final String NOTE_LIKE_KEY = "like:note"; + + /** + * redis key : 点赞问题 + */ + public static final String QUESTION_LIKE_KEY = "like:question"; + + /** + * redis key : 自测代码限流 + */ + public static final String TEST_RUN_CODE_KEY = "runcode:test:%s"; + + /** + * redis key : 提交代码限流 + */ + public static final String COMMIT_RUN_CODE_KEY = "runcode:commit:%s"; + + /** + * redis key : 消息模板 + */ + public static final String MESSAGE_TEMPLATE_KEY = "template_key:message"; + + /** + * redis key : 短信模板 + */ + public static final String SMS_TEMPLATE_KEY = "template_key:sms"; + + /** + * redis key : 邮件模板 + */ + public static final String EMAIL_TEMPLATE_KEY = "template_key:email"; +} diff --git a/quick-sse/src/main/java/com/quick/constant/SseEmitterConstant.java b/quick-sse/src/main/java/com/quick/constant/SseEmitterConstant.java new file mode 100644 index 00000000..70363e06 --- /dev/null +++ b/quick-sse/src/main/java/com/quick/constant/SseEmitterConstant.java @@ -0,0 +1,8 @@ +package com.quick.constant; + +public class SseEmitterConstant { + + public static final String TASK_RESULT = "yicode"; + public static final String CLIENT_ID = "CLIENT_ID"; + +} diff --git a/quick-sse/src/main/java/com/quick/controller/SseEmitterController.java b/quick-sse/src/main/java/com/quick/controller/SseEmitterController.java new file mode 100644 index 00000000..9a908150 --- /dev/null +++ b/quick-sse/src/main/java/com/quick/controller/SseEmitterController.java @@ -0,0 +1,53 @@ +package com.quick.controller; + +import com.quick.exception.BusinessException; +import com.quick.service.SseEmitterService; +import com.quick.vo.ChatRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@RestController +@RequestMapping("/sse") +public class SseEmitterController { + + @Autowired + private SseEmitterService sseEmitterService; + + + + + /** + * 创建SSE长链接 + * + * @param clientId 客户端唯一ID(如果为空,则由后端生成并返回给前端) + * @return org.springframework.web.servlet.mvc.method.annotation.SseEmitter + * @author re + * @date 2021/12/12 + **/ + @CrossOrigin //如果nginx做了跨域处理,此处可去掉 + @GetMapping("/CreateSseConnect") + public SseEmitter createSseConnect(@RequestParam(name = "clientId", required = false) String clientId) throws BusinessException { + return sseEmitterService.createSseConnect(clientId); + } + + /** + * 关闭SSE连接 + * + * @param clientId 客户端ID + * @author re + * @date 2021/12/13 + **/ + @GetMapping("/CloseSseConnect") + public String closeSseConnect(String clientId) { + sseEmitterService.closeSseConnect(clientId); + return "success"; + } + + + @PostMapping( "/chat/stream") + public SseEmitter chatStream(@RequestBody ChatRequest request) throws Exception { + return sseEmitterService.streamChat(request); + } + +} \ No newline at end of file diff --git a/quick-sse/src/main/java/com/quick/event/ChatGPTEventListener.java b/quick-sse/src/main/java/com/quick/event/ChatGPTEventListener.java new file mode 100644 index 00000000..50034cb1 --- /dev/null +++ b/quick-sse/src/main/java/com/quick/event/ChatGPTEventListener.java @@ -0,0 +1,101 @@ +package com.quick.event; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + + +@Slf4j +@AllArgsConstructor +public class ChatGPTEventListener extends EventSourceListener { + + private SseEmitter sseEmitter; + + private String traceId; + + private List answer = new ArrayList<>(); + + + public ChatGPTEventListener(SseEmitter sseEmitter, String traceId) { + this.sseEmitter = sseEmitter; + this.traceId = traceId; + } + + public ChatGPTEventListener() { + } + + @Override + public void onOpen(EventSource eventSource, Response response) { + log.info("OpenAI服务器连接成功!,traceId[{}]", traceId); + } + + + @SneakyThrows + @Override + public void onEvent(EventSource eventSource, String id, String type, String data) { + log.info("on event data: {}", data); + if (data.equals("[DONE]")) { + log.info("OpenAI服务器发送结束标志!,traceId[{}]", traceId); + + /** + * 1、完成token计算 + * 2、完成数据存储 + * 3、返回json格式,用于页面渲染 + */ + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]") + .reconnectTime(3000)); + return; + } + JSONObject jsonObject = JSONUtil.parseObj(data); + JSONArray choicesJsonArray = jsonObject.getJSONArray("choices"); + String content = null; + if (choicesJsonArray.isEmpty()) { + content = ""; + } else { + JSONObject choiceJson = choicesJsonArray.getJSONObject(0); + JSONObject deltaJson = choiceJson.getJSONObject("delta"); + String text = deltaJson.getStr("content"); + if (text != null) { + content = text; + answer.add(content); + sseEmitter.send(SseEmitter.event() + .data(content) + .reconnectTime(2000)); + } + } + + } + + + @Override + public void onClosed(EventSource eventSource) { + log.info("OpenAI服务器关闭连接!,traceId[{}]", traceId); + log.info("complete answer: {}", StrUtil.join("", answer)); + sseEmitter.complete(); + } + + + @SneakyThrows + @Override + public void onFailure(EventSource eventSource, Throwable t, Response response) { + // TODO 处理前端中断 + log.error("OpenAI服务器连接异常!response:[{}],traceId[{}] 当前内容:{}", response, traceId, StrUtil.join("", answer), t); + eventSource.cancel(); + sseEmitter.completeWithError(t); + } +} \ No newline at end of file diff --git a/quick-sse/src/main/java/com/quick/exception/BusinessException.java b/quick-sse/src/main/java/com/quick/exception/BusinessException.java new file mode 100644 index 00000000..50b15e94 --- /dev/null +++ b/quick-sse/src/main/java/com/quick/exception/BusinessException.java @@ -0,0 +1,22 @@ +package com.quick.exception; + +public class BusinessException extends Exception{ + public BusinessException() { + } + + public BusinessException(String message) { + super(message); + } + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } + + public BusinessException(Throwable cause) { + super(cause); + } + + public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/quick-sse/src/main/java/com/quick/service/SseEmitterService.java b/quick-sse/src/main/java/com/quick/service/SseEmitterService.java new file mode 100644 index 00000000..0235f828 --- /dev/null +++ b/quick-sse/src/main/java/com/quick/service/SseEmitterService.java @@ -0,0 +1,24 @@ +package com.quick.service; + +import com.quick.exception.BusinessException; +import com.quick.vo.ChatRequest; +import com.quick.vo.SseEmitterResultVO; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.List; + +public interface SseEmitterService { + SseEmitter streamChat(ChatRequest request); + + public SseEmitter createSseConnect(String clientId) throws BusinessException; + + public void closeSseConnect(String clientId) ; + + // 根据客户端id获取SseEmitter对象 + public SseEmitter getSseEmitterByClientId(String clientId); + + // 推送消息到客户端,此处结合业务代码,业务中需要推送消息处调用即可向客户端主动推送消息 + public void sendMsgToClient(List sseEmitterResultVOList) ; + + SseEmitter ask(String clientId) throws BusinessException; +} diff --git a/quick-sse/src/main/java/com/quick/service/SseEmitterServiceImpl.java b/quick-sse/src/main/java/com/quick/service/SseEmitterServiceImpl.java new file mode 100644 index 00000000..2cc2195a --- /dev/null +++ b/quick-sse/src/main/java/com/quick/service/SseEmitterServiceImpl.java @@ -0,0 +1,307 @@ +package com.quick.service; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSON; +import cn.hutool.json.JSONUtil; +import com.quick.constant.SseEmitterConstant; +import com.quick.event.ChatGPTEventListener; +import com.quick.exception.BusinessException; +import com.quick.vo.ChatRequest; +import com.quick.vo.SseEmitterResultVO; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import okhttp3.sse.EventSources; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +@Service +@Slf4j +public class SseEmitterServiceImpl implements SseEmitterService { + + private static final String URL = "https://api.openai.com/v1/chat/completions"; + + /** + * 容器,保存连接,用于输出返回 + */ + private static Map sseCache = new ConcurrentHashMap<>(); + + private final ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); + + @Autowired + private OkHttpClient okHttpClient; + + @Value("${chatgpt.key:}") + private String chatgptKey; + + + @Override + @SneakyThrows + public SseEmitter streamChat(ChatRequest request) { + request.setStream(true); + SseEmitter sseEmitter = new SseEmitter(300000L); + sseEmitter.onCompletion(() -> log.info("答案已返回,请处理后续逻辑")); + String traceId = UUID.randomUUID().toString(); + sseEmitter.send(SseEmitter.event().id(traceId).name("update")); + ChatGPTEventListener chatGPTEventListener = new ChatGPTEventListener(sseEmitter,traceId); + streamChat(request,chatGPTEventListener,traceId); + log.info("请求已结束"); + return sseEmitter; + } + + public void streamChat(ChatRequest request, EventSourceListener eventSourceListener, String traceId) { + try { + EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); + + Request apiRequest = new Request.Builder() + .url(URL) + .post(RequestBody.create(JSONUtil.toJsonStr(request), okhttp3.MediaType.parse("application/json"))) + .header("Authorization", "Bearer " + chatgptKey) + .build(); + factory.newEventSource(apiRequest, eventSourceListener); + } catch (Exception e) { + log.error("请求服务器异常!:traceId{}", traceId, e); + } + } + + + + @Override + public SseEmitter createSseConnect(String clientId) throws BusinessException { + // 设置超时时间,0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutException + SseEmitter sseEmitter = new SseEmitter(0L); + // 是否需要给客户端推送ID + if (StrUtil.isBlank(clientId)) { + clientId = IdUtil.simpleUUID(); + } + // 注册回调 + sseEmitter.onCompletion(completionCallBack(clientId)); + sseCache.put(clientId, sseEmitter); + log.info("创建新的sse连接,当前用户:{}", clientId); + + try { + +// sseEmitter.send(SseEmitter.event().id(SseEmitterConstant.CLIENT_ID).data(clientId+i)); + cachedThreadPool.execute(() -> { + try { + for (int i = 0; i < 10; i++) { + sseEmitter.send(IdUtil.simpleUUID()); + TimeUnit.SECONDS.sleep(1); + } + sseEmitter.complete(); + } catch (Exception e) { + sseEmitter.completeWithError(e); + log.error("SseEmitterServiceImpl->createSseConnect error", e); + } + }); + + + } catch (Exception e) { + log.error("SseEmitterServiceImpl[createSseConnect]: 创建长链接异常,客户端ID:{}", clientId, e); + throw new BusinessException("创建连接异常!", e); + } + return sseEmitter; + } + + @Override + public void closeSseConnect(String clientId) { + SseEmitter sseEmitter = sseCache.get(clientId); + if (sseEmitter != null) { + sseEmitter.complete(); + removeUser(clientId); + } + } + + // 根据客户端id获取SseEmitter对象 + @Override + public SseEmitter getSseEmitterByClientId(String clientId) { + return sseCache.get(clientId); + } + + // 推送消息到客户端,此处结合业务代码,业务中需要推送消息处调用即可向客户端主动推送消息 + @Override + public void sendMsgToClient(List sseEmitterResultVOList) { + if (CollectionUtil.isEmpty(sseCache)) { + return; + } + for (Map.Entry entry : sseCache.entrySet()) { + sendMsgToClientByClientId(entry.getKey(), sseEmitterResultVOList, entry.getValue()); + } + } + + @Override + public SseEmitter ask(String clientId) throws BusinessException { + SseEmitter sseEmitter = new SseEmitter(0L); + if (StrUtil.isBlank(clientId)) { + clientId = IdUtil.simpleUUID(); + } + // 注册回调 + sseEmitter.onCompletion(completionCallBack(clientId)); + sseCache.put(clientId, sseEmitter); + log.info("创建新的sse连接,当前用户:{}", clientId); + + try { + // 主线程不能阻塞 + sseEmitter.send(SseEmitter.event().id(SseEmitterConstant.CLIENT_ID).data(clientId)); + cachedThreadPool.execute(() -> { + try { + for (int i = 0; i < 10; i++) { + String now = DateUtil.now(); + sseEmitter.send(now+": "+IdUtil.simpleUUID()); + TimeUnit.SECONDS.sleep(1); + } + sseEmitter.complete(); + } catch (Exception e) { + sseEmitter.completeWithError(e); + log.error("SseEmitterServiceImpl->createSseConnect error", e); + } + }); +// while (cachedThreadPool.awaitTermination(10,TimeUnit.SECONDS)); +// for (int i = 0; i < 10; i++) { +// String now = DateUtil.now(); +// sseEmitter.send(now+": "+IdUtil.simpleUUID()); +// TimeUnit.SECONDS.sleep(1); +// } +// sseEmitter.complete(); + log.info("main thread"); + } catch (Exception e) { + log.error("SseEmitterServiceImpl[createSseConnect]: 创建长链接异常,客户端ID:{}", clientId, e); + throw new BusinessException("创建连接异常!", e); + } + return sseEmitter; + } + + /** + * 推送消息到客户端 + * 此处做了推送失败后,重试推送机制,可根据自己业务进行修改 + * + * @param clientId 客户端ID + * @param sseEmitterResultVOList 推送信息,此处结合具体业务,定义自己的返回值即可 + * @author re + * @date 2022/3/30 + **/ + private void sendMsgToClientByClientId(String clientId, List sseEmitterResultVOList, SseEmitter sseEmitter) { + if (sseEmitter == null) { + log.error("SseEmitterServiceImpl[sendMsgToClient]: 推送消息失败:客户端{}未创建长链接,失败消息:{}", + clientId, sseEmitterResultVOList.toString()); + return; + } + + SseEmitter.SseEventBuilder sendData = SseEmitter.event().id(SseEmitterConstant.TASK_RESULT).data(sseEmitterResultVOList, MediaType.APPLICATION_JSON); + try { + sseEmitter.send(sendData); + } catch (IOException e) { + // 推送消息失败,记录错误日志,进行重推 + log.error("SseEmitterServiceImpl[sendMsgToClient]: 推送消息失败:{},尝试进行重推", sseEmitterResultVOList.toString(), e); + boolean isSuccess = true; + // 推送消息失败后,每隔10s推送一次,推送5次 + for (int i = 0; i < 5; i++) { + try { + Thread.sleep(10000); + sseEmitter = sseCache.get(clientId); + if (sseEmitter == null) { + log.error("SseEmitterServiceImpl[sendMsgToClient]:{}的第{}次消息重推失败,未创建长链接", clientId, i + 1); + continue; + } + sseEmitter.send(sendData); + } catch (Exception ex) { + log.error("SseEmitterServiceImpl[sendMsgToClient]:{}的第{}次消息重推失败", clientId, i + 1, ex); + continue; + } + log.info("SseEmitterServiceImpl[sendMsgToClient]:{}的第{}次消息重推成功,{}", clientId, i + 1, sseEmitterResultVOList.toString()); + return; + } + } + } + + /** + * 长链接完成后回调接口(即关闭连接时调用) + * + * @param clientId 客户端ID + * @return java.lang.Runnable + * @author re + * @date 2021/12/14 + **/ + private Runnable completionCallBack(String clientId) { + return () -> { + log.info("结束连接:{}", clientId); + removeUser(clientId); + }; + } + + /** + * 连接超时时调用 + * + * @param clientId 客户端ID + * @return java.lang.Runnable + * @author re + * @date 2021/12/14 + **/ + private Runnable timeoutCallBack(String clientId) { + return () -> { + log.info("连接超时:{}", clientId); + removeUser(clientId); + }; + } + + /** + * 推送消息异常时,回调方法 + * + * @param clientId 客户端ID + * @return java.util.function.Consumer + * @author re + * @date 2021/12/14 + **/ + private Consumer errorCallBack(String clientId) { + return throwable -> { + log.error("SseEmitterServiceImpl[errorCallBack]:连接异常,客户端ID:{}", clientId); + + // 推送消息失败后,每隔10s推送一次,推送5次 + for (int i = 0; i < 5; i++) { + try { + Thread.sleep(10000); + SseEmitter sseEmitter = sseCache.get(clientId); + if (sseEmitter == null) { + log.error("SseEmitterServiceImpl[errorCallBack]:第{}次消息重推失败,未获取到 {} 对应的长链接", i + 1, clientId); + continue; + } + sseEmitter.send("失败后重新推送"); + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + } + + /** + * 移除用户连接 + * + * @param clientId 客户端ID + * @author re + * @date 2021/12/14 + **/ + private void removeUser(String clientId) { + sseCache.remove(clientId); + log.info("SseEmitterServiceImpl[removeUser]:移除用户:{}", clientId); + } +} \ No newline at end of file diff --git a/quick-sse/src/main/java/com/quick/vo/ChatRequest.java b/quick-sse/src/main/java/com/quick/vo/ChatRequest.java new file mode 100644 index 00000000..6f7ba3ce --- /dev/null +++ b/quick-sse/src/main/java/com/quick/vo/ChatRequest.java @@ -0,0 +1,54 @@ +package com.quick.vo; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ChatRequest { + + @Schema(description = "模型ID,目前只支持gpt-3.5-turbo和gpt-3.5-turbo-0301", example = "gpt-3.5-turbo", required = true) + private String model; + + @Schema(description = "要为其生成聊天内容的消息列表", required = true) + private List messages; + + @Schema(description = "抽样温度,范围为0到2。较高的值(如0.8)会使输出更随机,而较低的值(如0.2)会使其更专注和确定性。") + private Double temperature; + + @Schema(description = "top-p策略的概率阈值,只保留该阈值内的概率之和,范围为0到1。与抽样温度二选一。") + private Double topP; + + @Schema(description = "每个输入消息要生成的聊天完成选择数量。") + private Integer n; + + @Schema(description = "是否使用流式传输,如果设置,则会发送部分消息增量,就像ChatGPT一样。Token将作为仅包含数据的服务器发送事件发送,随着它们可用,流将由data: [DONE]消息终止。") + private Boolean stream; + + @Schema(description = "最多允许生成的答案的最大标记数。默认情况下,模型可以返回的标记数为(4096 -提示标记数)。") + private Integer maxTokens; + + @Schema(description = "基于它们是否已在到目前为止的文本中出现,对新标记进行惩罚的数字,介于-2.0和2.0之间的数字。正值会增加模型谈论新话题的可能性。") + private Double presencePenalty; + + @Schema(description = "基于它们到目前为止在文本中的现有频率对新标记进行惩罚的数字,介于-2.0和2.0之间的数字。正值会减少模型直接重复相同内容的可能性。") + private Double frequencyPenalty; + + @Schema(description = "修改特定标记出现在完成中的可能性。接受将标记(由其在标记器中的标记ID指定)映射到-100到100的关联偏差值的json对象。在采样之前,数学上将偏差添加到模型生成的标志物中的logit。确切的效果将因模型而异,但值介于-1和1之间应该会减少或增加选择的可能性;值为-100或100应该导致禁止或专有选择相关标记。") + private JSONObject logitBias; + + @Schema(description = "用于表示终端用户的唯一标识符,可帮助OpenAI监视和检测滥用。了解更多。") + private String user; + + @Schema(description = "当达到以下任意一个序列时,API将停止生成进一步的标记。") + private JSONArray stop; +} \ No newline at end of file diff --git a/quick-sse/src/main/java/com/quick/vo/Message.java b/quick-sse/src/main/java/com/quick/vo/Message.java new file mode 100644 index 00000000..ab663c73 --- /dev/null +++ b/quick-sse/src/main/java/com/quick/vo/Message.java @@ -0,0 +1,13 @@ +package com.quick.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Message { + private String content; + private String role; +} \ No newline at end of file diff --git a/quick-sse/src/main/java/com/quick/vo/SseEmitterResultVO.java b/quick-sse/src/main/java/com/quick/vo/SseEmitterResultVO.java new file mode 100644 index 00000000..cc50997e --- /dev/null +++ b/quick-sse/src/main/java/com/quick/vo/SseEmitterResultVO.java @@ -0,0 +1,9 @@ +package com.quick.vo; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class SseEmitterResultVO implements Serializable { +} diff --git a/quick-sse/src/main/resources/application.yml b/quick-sse/src/main/resources/application.yml new file mode 100644 index 00000000..3b4429d8 --- /dev/null +++ b/quick-sse/src/main/resources/application.yml @@ -0,0 +1,9 @@ +server: + port: 8080 + +proxy: + hostname: 127.0.0.1 + port: 1080 + +chatgpt: + key: \ No newline at end of file diff --git a/quick-sse/src/test/java/com/quick/AppTest.java b/quick-sse/src/test/java/com/quick/AppTest.java new file mode 100644 index 00000000..eb9df650 --- /dev/null +++ b/quick-sse/src/test/java/com/quick/AppTest.java @@ -0,0 +1,38 @@ +package com.quick; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/quick-starter-demo/pom.xml b/quick-starter-demo/pom.xml new file mode 100644 index 00000000..545d0dca --- /dev/null +++ b/quick-starter-demo/pom.xml @@ -0,0 +1,23 @@ + + + quick-starter-demo + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + + quick-starter + com.quick + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-test + 2.0.4.RELEASE + + + + \ No newline at end of file diff --git a/quick-starter-demo/src/main/java/com/quick/starter/demo/StarterApplication.java b/quick-starter-demo/src/main/java/com/quick/starter/demo/StarterApplication.java new file mode 100644 index 00000000..6f43534a --- /dev/null +++ b/quick-starter-demo/src/main/java/com/quick/starter/demo/StarterApplication.java @@ -0,0 +1,17 @@ +package com.quick.starter.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2019-08-06 21:28 + * + */ +@SpringBootApplication +public class StarterApplication { + public static void main(String[] args) { + SpringApplication.run(StarterApplication.class); + } +} diff --git a/quick-starter-demo/src/test/java/com/quick/starter/ApplicationTest.java b/quick-starter-demo/src/test/java/com/quick/starter/ApplicationTest.java new file mode 100644 index 00000000..6bbd4346 --- /dev/null +++ b/quick-starter-demo/src/test/java/com/quick/starter/ApplicationTest.java @@ -0,0 +1,26 @@ +package com.quick.starter; + +import com.quick.starter.autoconfigure.HelloService; +import com.quick.starter.demo.StarterApplication; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author vector + * @date: 2019/8/6 0006 19:26 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = StarterApplication.class) +public class ApplicationTest { + + @Autowired + private HelloService helloService; + + @Test + public void testStarter() { + System.out.println(helloService.hell()); + } +} diff --git a/quick-starter-demo/src/test/resources/application.yml b/quick-starter-demo/src/test/resources/application.yml new file mode 100644 index 00000000..08cc2d12 --- /dev/null +++ b/quick-starter-demo/src/test/resources/application.yml @@ -0,0 +1,4 @@ + + +quick: + name: wxc \ No newline at end of file diff --git a/quick-starter/pom.xml b/quick-starter/pom.xml new file mode 100644 index 00000000..4cc7e765 --- /dev/null +++ b/quick-starter/pom.xml @@ -0,0 +1,29 @@ + + + quick-starter + 1.0-SNAPSHOT + 4.0.0 + jar + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-autoconfigure + + + + \ No newline at end of file diff --git a/quick-starter/src/main/java/com/quick/starter/autoconfigure/BannerApplication.java b/quick-starter/src/main/java/com/quick/starter/autoconfigure/BannerApplication.java new file mode 100644 index 00000000..f4b86404 --- /dev/null +++ b/quick-starter/src/main/java/com/quick/starter/autoconfigure/BannerApplication.java @@ -0,0 +1,17 @@ +package com.quick.starter.autoconfigure; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2022/1/23 10:04 PM + * + */ +@SpringBootApplication +public class BannerApplication { + public static void main(String[] args) { + SpringApplication.run(BannerApplication.class); + } +} diff --git a/quick-starter/src/main/java/com/quick/starter/autoconfigure/HelloService.java b/quick-starter/src/main/java/com/quick/starter/autoconfigure/HelloService.java new file mode 100644 index 00000000..2f45c085 --- /dev/null +++ b/quick-starter/src/main/java/com/quick/starter/autoconfigure/HelloService.java @@ -0,0 +1,21 @@ +package com.quick.starter.autoconfigure; + +/** + * @author vector + * @date: 2019/8/6 0006 17:08 + */ +public class HelloService { + private String msg; + + public String hell() { + return "hello " + msg; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} diff --git a/quick-starter/src/main/java/com/quick/starter/autoconfigure/QuickAutoConfiguration.java b/quick-starter/src/main/java/com/quick/starter/autoconfigure/QuickAutoConfiguration.java new file mode 100644 index 00000000..f0784f19 --- /dev/null +++ b/quick-starter/src/main/java/com/quick/starter/autoconfigure/QuickAutoConfiguration.java @@ -0,0 +1,32 @@ +package com.quick.starter.autoconfigure; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author vector + * @date: 2019/8/6 0006 16:53 + */ +@Configuration +@ConditionalOnClass(HelloService.class) +@EnableConfigurationProperties(QuickProperties.class) +public class QuickAutoConfiguration { + + @Autowired + private QuickProperties quickProperties; + + + @Bean + @ConditionalOnMissingBean(HelloService.class) + public HelloService helloService(){ + System.out.println("Execute Create New Bean"); + HelloService helloService = new HelloService(); + helloService.setMsg(quickProperties.getName()); + return helloService; + } + +} diff --git a/quick-starter/src/main/java/com/quick/starter/autoconfigure/QuickProperties.java b/quick-starter/src/main/java/com/quick/starter/autoconfigure/QuickProperties.java new file mode 100644 index 00000000..4c937f41 --- /dev/null +++ b/quick-starter/src/main/java/com/quick/starter/autoconfigure/QuickProperties.java @@ -0,0 +1,21 @@ +package com.quick.starter.autoconfigure; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author vector + * @date: 2019/8/6 0006 17:00 + */ +@ConfigurationProperties(prefix = "quick") +public class QuickProperties { + + private String name = "quick starter demo!!!"; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/quick-starter/src/main/resources/META-INF/spring.factories b/quick-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..47f36c88 --- /dev/null +++ b/quick-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.quick.starter.autoconfigure.QuickAutoConfiguration \ No newline at end of file diff --git a/quick-starter/src/main/resources/banner.txt b/quick-starter/src/main/resources/banner.txt new file mode 100644 index 00000000..ba5c244f --- /dev/null +++ b/quick-starter/src/main/resources/banner.txt @@ -0,0 +1,9 @@ + + ____ _ _ _____ _ _ + / __ \ (_) | | / ____| | | | + | | | |_ _ _ ___| | _______| (___ | |_ __ _ _ __| |_ ___ _ __ + | | | | | | | |/ __| |/ /______\___ \| __/ _` | '__| __/ _ \ '__| + | |__| | |_| | | (__| < ____) | || (_| | | | || __/ | + \___\_\\__,_|_|\___|_|\_\ |_____/ \__\__,_|_| \__\___|_| + + diff --git a/quick-swagger/README.md b/quick-swagger/README.md new file mode 100644 index 00000000..46f213cc --- /dev/null +++ b/quick-swagger/README.md @@ -0,0 +1,185 @@ +### Swagger介绍 +  每每get新的技能想分享的时候,按照套路来讲,需要有一个版块将该技能的“前世今生”介绍个遍,但就我接触到完成配置不超过半小时,我觉得让我完完整整的介绍有点太虚了,所以,最好的介绍就是下面的官网 +http://swagger.io/ + +http://swagger.io/irc/ 这个是实时聊天室,刚刚和老外沟通了一番“how are you?fine thk you.and you?” + +https://github.com/swagger-api/swagger-core/wiki/Annotations#apimodel 这个是一些注解的api + +Swagger有三个模块 + +- Swagger Editor +- Swagger UI +- Swagger Codegen + +  我使用的是Swagger UI,我个人的理解就是“使用Swagger相关的注解并启动服务后,就可以在对应的页面查看API并**测试**”,先看一下最终的界面 +   +![api.png](http://upload-images.jianshu.io/upload_images/3167229-ec0d13bf3b0c9da5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +接口描述、参数类型、返回示例在线调试都给你搞定了。你还在犹豫什么,赶快checkou代码,试一试吧 + +### 整合 +  我接着之前的代码的写(可以在我的[GitHub](https://github.com/vector4wang/springbootquick)上浏览,或者直接clone到本地再切换到**api-norms**分支),这里要说一下,使用Springboot整合Swagger贼JB简单,相比较而言,SpringMVC就比较复杂了,这里暂且不谈(以后可能也不会谈了,自从我使用了Springboot之后,就已经开始抛弃SpringMVC了) + +#### maven依赖 +老规矩上配置 +```xml + + io.springfox + springfox-swagger2 + ${swagger.version} + + + io.springfox + springfox-swagger-ui + ${swagger.version} + +``` + +#### 添加`Swagger`注解 +  在Application上直接添加`@EnableSwagger2`,注意版本,官网上的版本还没有更新到最新的,最新的在[Github](https://github.com/swagger-api/swagger-ui)上看,配置后的代码 +```java +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +/** + * Created by wangxc on 2017/3/9. + */ +@SpringBootApplication +@EnableSwagger2 +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} + +``` +可以了,接下来就是描述接口的注解了!在Controller层,做如下配置 +```java +package com.quick.api; + + +import com.quick.po.Address; +import com.quick.utils.BaseResp; +import com.quick.utils.ResultStatus; +import io.swagger.annotations.*; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created with IDEA + * User: vector + * Data: 2017/4/12 + * Time: 8:41 + * Description: + */ +@RestController +@RequestMapping("/api") +@Api("springboot整合swagger2") // Swagger UI 对应api的标题描述 +public class WebController { + + @ApiOperation("获取地址信息") // 单个接口的描述 + @ApiImplicitParams({ + @ApiImplicitParam(paramType="query",name="province",dataType="String",required=true,value="省",defaultValue="广东省"),// 每个参数的类型,名称,数据类型,是否校验,描述,默认值(这些在界面上有展示) + @ApiImplicitParam(paramType="query",name="area",dataType="String",required=true,value="地区",defaultValue="南山区"), + @ApiImplicitParam(paramType="query",name="street",dataType="String",required=true,value="街道",defaultValue="桃园路"), + @ApiImplicitParam(paramType="query",name="num",dataType="String",required=true,value="门牌号",defaultValue="666") + }) + @ApiResponses({ + @ApiResponse(code=400,message="请求参数没填好"), // 响应对应编码的描述 + @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对") + }) + @RequestMapping(value = "/address",method = RequestMethod.POST) + public BaseResp
getAddressInfo(@RequestParam(value = "province")String province, + @RequestParam(value = "area")String area, + @RequestParam(value = "street")String street, + @RequestParam(value = "num")String num){ + + if(StringUtils.isEmpty(province)||StringUtils.isEmpty(area)||StringUtils.isEmpty(street)||StringUtils.isEmpty(num)){ + return new BaseResp(ResultStatus.error_invalid_argument); + } + Address address = new Address(); + address.setProvince(province); + address.setArea(area); + address.setStreet(street); + address.setNum(num); + return new BaseResp(ResultStatus.SUCCESS,address); + + } + + @ApiOperation("获取地址信息") + @ApiImplicitParams({ + @ApiImplicitParam(paramType="query",name="province",dataType="String",required=true,value="省",defaultValue="广东省"), + @ApiImplicitParam(paramType="query",name="area",dataType="String",required=true,value="地区",defaultValue="南山区"), + @ApiImplicitParam(paramType="query",name="street",dataType="String",required=true,value="街道",defaultValue="桃园路"), + @ApiImplicitParam(paramType="query",name="num",dataType="String",required=true,value="门牌号",defaultValue="666") + }) + @ApiResponses({ + @ApiResponse(code=400,message="请求参数没填好"), + @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对") + }) + @RequestMapping(value = "/address/list",method = RequestMethod.POST) + public BaseResp> getAddressList(@RequestParam(value = "province")String province, + @RequestParam(value = "area")String area, + @RequestParam(value = "street")String street, + @RequestParam(value = "num")String num){ + + if(StringUtils.isEmpty(province)||StringUtils.isEmpty(area)||StringUtils.isEmpty(street)||StringUtils.isEmpty(num)){ + return new BaseResp(ResultStatus.error_invalid_argument); + } + Address address = new Address(); + address.setProvince(province); + address.setArea(area); + address.setStreet(street); + address.setNum(num); + List
lists = new ArrayList<>(); + lists.add(address); + lists.add(address); + lists.add(address); + return new BaseResp(ResultStatus.SUCCESS,lists); + + } +} +``` + +我只是在原来的基础上添加了下面注解 + +名称|解释 +:---|--- +@Api()|将类标记为一种Swagger资源。 +@ApiOperation()|描述针对特定路径的操作或通常是 http 方法。 +@ApiImplicitParams|允许多个 ApiImplicitParam 对象列表的包装。 +@ApiImplicitParam|表示 api 操作中的单个参数。 +@ApiResponses|允许多个 ApiResponse 对象列表的包装。 +@ApiResponse|描述操作的可能响应。 + +更多的[看这里](https://github.com/swagger-api/swagger-core/wiki/Annotations#apimodel) + +就这么简单,一个基本而又强大的API文档就整理好了! + +### 启动 + +正常的启动SpringBoot,你会发现控制台输出了这些内容 +```console +2017-05-03 21:42:52,975 INFO ClassOrApiAnnotationResourceGrouping:100 - Group for method getAddressList was springboot整合swagger2 +2017-05-03 21:42:52,986 INFO ClassOrApiAnnotationResourceGrouping:100 - Group for method getAddressList was springboot整合swagger2 +``` +说明Swagger已经成功跑起来了,接下来打开浏览器,输入你链接 +**yourdomain/swagger-ui.html** +我的是**http://localhost:8080/swagger-ui.html** + +相信你看了界面并四处点点之后,就会对上面注解的含义有了更进一步的了解~ + +### 后记 +  这里展示的只是Swagger最基本的功能,更多强大的功能如果后面有运用,我会持续更新的。 +  目前我在看api寻找SwaggerUI输入文件的测试,因为我有个接口需要上传文件,等我搞定,再来分享吧!!! +   + +欢迎进入我的[个人博客](https://www.jianshu.com/p/e326914418eb)浏览 diff --git a/quick-swagger/pom.xml b/quick-swagger/pom.xml index dfd8a65c..eea44d9e 100644 --- a/quick-swagger/pom.xml +++ b/quick-swagger/pom.xml @@ -12,27 +12,17 @@ org.springframework.boot spring-boot-starter-parent - 1.3.2.RELEASE + 1.4.3.RELEASE - 2.2.2 + 2.7.0 org.springframework.boot spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - - - org.springframework.boot - spring-boot-starter-log4j org.springframework.boot @@ -40,14 +30,12 @@ test - - - net.sf.json-lib - json-lib - 2.4 - jdk15 - + + + + + io.springfox @@ -59,18 +47,12 @@ springfox-swagger-ui ${swagger.version} - - - org.slf4j - slf4j-log4j12 - 1.7.22 - junit junit - 4.12 + 4.13.1 test @@ -93,5 +75,4 @@ - \ No newline at end of file diff --git a/quick-swagger/src/main/java/com/quick/Application.java b/quick-swagger/src/main/java/com/quick/Application.java index 920a275a..0957c948 100644 --- a/quick-swagger/src/main/java/com/quick/Application.java +++ b/quick-swagger/src/main/java/com/quick/Application.java @@ -7,6 +7,7 @@ import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @@ -15,26 +16,22 @@ @EnableSwagger2 public class Application { - @Bean - public Docket createRestApi() { - return new Docket(DocumentationType.SWAGGER_2) - .apiInfo(apiInfo()) - .select() - .apis(RequestHandlerSelectors.basePackage("com.quick.api")) //扫描API的包路径 - .paths(PathSelectors.any()) - .build(); - } - private ApiInfo apiInfo() { - return new ApiInfoBuilder() - .title("SpringBoot整合Swagger2") // 标题 - .description("api接口的文档整理,支持在线测试") // 描述 - .termsOfServiceUrl("http://vector4wang.tk/") //网址 - .contact("Vector.Wang") // 作者 - .version("1.0") // 版本号 - .build(); - } + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select() + .apis(RequestHandlerSelectors.basePackage("com.quick")) //扫描API的包路径 + .paths(PathSelectors.any()).build(); + } - public static void main(String[] args) { - SpringApplication.run(Application.class); - } + private ApiInfo apiInfo() { + return new ApiInfoBuilder().title("SpringBoot整合Swagger2") // 标题 + .description("api接口的文档整理,支持在线测试") // 描述 + .contact(new Contact("vector.wang", "http://blog.wangxc.club/", "vector4wang@qq.com")) + .version("1.0") // 版本号 + .build(); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class); + } } \ No newline at end of file diff --git a/quick-swagger/src/main/java/com/quick/api/WebController.java b/quick-swagger/src/main/java/com/quick/api/WebController.java index 9aec3155..53020906 100644 --- a/quick-swagger/src/main/java/com/quick/api/WebController.java +++ b/quick-swagger/src/main/java/com/quick/api/WebController.java @@ -21,97 +21,86 @@ */ @RestController @RequestMapping("/api") -@Api(value = "api",description = "WebController") // Swagger UI 对应api的标题描述 +@Api(value = "api", description = "WebController") // Swagger UI 对应api的标题描述 public class WebController { - @ApiOperation("获取地址信息") // 单个接口的描述 - @ApiImplicitParams({ - @ApiImplicitParam(paramType="query",name="province",dataType="String",required=true,value="省",defaultValue="广东省"),// 每个参数的类型,名称,数据类型,是否校验,描述,默认值(这些在界面上有展示) - @ApiImplicitParam(paramType="query",name="area",dataType="String",required=true,value="地区",defaultValue="南山区"), - @ApiImplicitParam(paramType="query",name="street",dataType="String",required=true,value="街道",defaultValue="桃园路"), - @ApiImplicitParam(paramType="query",name="num",dataType="String",required=true,value="门牌号",defaultValue="666") - }) - @ApiResponses({ - @ApiResponse(code=400,message="请求参数没填好"), // 响应对应编码的描述 - @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对") - }) - @RequestMapping(value = "/address",method = RequestMethod.POST) - public BaseResp
getAddressInfo(@RequestParam(value = "province")String province, - @RequestParam(value = "area")String area, - @RequestParam(value = "street")String street, - @RequestParam(value = "num")String num){ + @ApiOperation(value = "获取地址信息", notes = "我是描述") + @ApiImplicitParams({ + @ApiImplicitParam(paramType = "body", name = "address", dataType = "Address", required = true, value = "省", defaultValue = "广东省"), + }) + @PostMapping(value = "/address") + public BaseResp
getAddressInfo(@RequestBody Address address) { + BaseResp
resp = new BaseResp
(); + resp.setMessage(ResultStatus.SUCCESS.getErrorMsg()); + resp.setCode(ResultStatus.SUCCESS.getErrorCode()); + resp.setData(address); + return resp; + } - if(StringUtils.isEmpty(province)||StringUtils.isEmpty(area)||StringUtils.isEmpty(street)||StringUtils.isEmpty(num)){ - return new BaseResp(ResultStatus.error_invalid_argument); - } - Address address = new Address(); - address.setProvince(province); - address.setArea(area); - address.setStreet(street); - address.setNum(num); - return new BaseResp(ResultStatus.SUCCESS,address); + @PostMapping(value = "/address2") + public BaseResp
getAddressInfo2() { - } + Address address = new Address(); + address.setProvince("wangxc"); + address.setArea("wangxc"); + address.setStreet("wangxc"); + address.setNum("wangxc"); + BaseResp
resp = new BaseResp<>(ResultStatus.SUCCESS, address); + return resp; - @ApiOperation("获取地址信息") - @ApiImplicitParams({ - @ApiImplicitParam(paramType="query",name="province",dataType="String",required=true,value="省",defaultValue="广东省"), - @ApiImplicitParam(paramType="query",name="area",dataType="String",required=true,value="地区",defaultValue="南山区"), - @ApiImplicitParam(paramType="query",name="street",dataType="String",required=true,value="街道",defaultValue="桃园路"), - @ApiImplicitParam(paramType="query",name="num",dataType="String",required=true,value="门牌号",defaultValue="666") - }) - @ApiResponses({ - @ApiResponse(code=400,message="请求参数没填好"), - @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对") - }) - @RequestMapping(value = "/address/list",method = RequestMethod.POST) - public BaseResp> getAddressList(@RequestParam(value = "province")String province, - @RequestParam(value = "area")String area, - @RequestParam(value = "street")String street, - @RequestParam(value = "num")String num){ + } - if(StringUtils.isEmpty(province)||StringUtils.isEmpty(area)||StringUtils.isEmpty(street)||StringUtils.isEmpty(num)){ - return new BaseResp(ResultStatus.error_invalid_argument); - } - Address address = new Address(); - address.setProvince(province); - address.setArea(area); - address.setStreet(street); - address.setNum(num); - List
lists = new ArrayList<>(); - lists.add(address); - lists.add(address); - lists.add(address); - return new BaseResp(ResultStatus.SUCCESS,lists); + @ApiOperation("获取地址信息") + @ApiImplicitParams({ + @ApiImplicitParam(paramType = "query", name = "province", dataType = "String", required = true, value = "省", defaultValue = "广东省"), + @ApiImplicitParam(paramType = "query", name = "area", dataType = "String", required = true, value = "地区", defaultValue = "南山区"), + @ApiImplicitParam(paramType = "query", name = "street", dataType = "String", required = true, value = "街道", defaultValue = "桃园路"), + @ApiImplicitParam(paramType = "query", name = "num", dataType = "String", required = true, value = "门牌号", defaultValue = "666")}) + @ApiResponses({@ApiResponse(code = 400, message = "请求参数没填好"), + @ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对")}) + @RequestMapping(value = "/address/list", method = RequestMethod.POST) + public BaseResp> getAddressList(@RequestParam(value = "province") String province, + @RequestParam(value = "area") String area, @RequestParam(value = "street") String street, + @RequestParam(value = "num") String num) { - } + if (StringUtils.isEmpty(province) || StringUtils.isEmpty(area) || StringUtils.isEmpty(street) || StringUtils + .isEmpty(num)) { + return new BaseResp(ResultStatus.error_invalid_argument); + } + Address address = new Address(); + address.setProvince(province); + address.setArea(area); + address.setStreet(street); + address.setNum(num); + List
lists = new ArrayList<>(); + lists.add(address); + lists.add(address); + lists.add(address); + return new BaseResp(ResultStatus.SUCCESS, lists); - @ApiOperation("获取地址信息(参数体)") - @ApiResponses({ - @ApiResponse(code=400,message="请求参数没填好"), - @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对") - }) - @RequestMapping(value = "/address/area/list",method = RequestMethod.POST) - public BaseResp getAddressList(@RequestBody ParaModel paraModel){ - return new BaseResp(ResultStatus.SUCCESS,paraModel.toString()); - } + } - @ApiOperation("获取地址信息(路径传参)") - @ApiImplicitParams({ - @ApiImplicitParam(paramType="path",name="area",dataType="String",required=true,value="区域",defaultValue="南山区"), - @ApiImplicitParam(paramType="path",name="number",dataType="String",required=true,value="门牌号",defaultValue="9527") - }) - @ApiResponses({ - @ApiResponse(code=400,message="请求参数没填好"), - @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对") - }) - @RequestMapping(value = "/address/{area}/{number}",method = RequestMethod.GET) - public BaseResp getAddress(@PathVariable("area")String area,@PathVariable("number")String number){ - Address address = new Address(); - address.setProvince("广东省"); - address.setArea(area); - address.setStreet("桃园街道"); - address.setNum(number); - return new BaseResp(ResultStatus.SUCCESS,address.toString()); - } + @ApiOperation("获取地址信息(参数体)") + @ApiResponses({@ApiResponse(code = 400, message = "请求参数没填好"), + @ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对")}) + @RequestMapping(value = "/address/area/list", method = RequestMethod.POST) + public BaseResp getAddressList(@RequestBody ParaModel paraModel) { + return new BaseResp(ResultStatus.SUCCESS, paraModel.toString()); + } + + @ApiOperation("获取地址信息(路径传参)") + @ApiImplicitParams({ + @ApiImplicitParam(paramType = "path", name = "area", dataType = "String", required = true, value = "区域", defaultValue = "南山区"), + @ApiImplicitParam(paramType = "path", name = "number", dataType = "String", required = true, value = "门牌号", defaultValue = "9527")}) + @ApiResponses({@ApiResponse(code = 400, message = "请求参数没填好"), + @ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对")}) + @RequestMapping(value = "/address/{area}/{number}", method = RequestMethod.GET) + public BaseResp getAddress(@PathVariable("area") String area, @PathVariable("number") String number) { + Address address = new Address(); + address.setProvince("广东省"); + address.setArea(area); + address.setStreet("桃园街道"); + address.setNum(number); + return new BaseResp(ResultStatus.SUCCESS, address.toString()); + } } diff --git a/quick-swagger/src/main/java/com/quick/po/Address.java b/quick-swagger/src/main/java/com/quick/po/Address.java index 46ffe3df..80090b86 100644 --- a/quick-swagger/src/main/java/com/quick/po/Address.java +++ b/quick-swagger/src/main/java/com/quick/po/Address.java @@ -1,5 +1,9 @@ package com.quick.po; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; + /** * Created with IDEA * User: vector @@ -7,16 +11,30 @@ * Time: 8:43 * Description: */ -public class Address { +public class Address implements Serializable { + + @ApiModelProperty(name = "province",example = "广东省") private String province; + + @ApiModelProperty(name = "area",example = "地域") private String area; + + @ApiModelProperty(name = "street",example = "街道") private String street; + + @ApiModelProperty(name = "num",example = "号码") private String num; public Address(){} + public Address(String province, String area, String street, String num) { + this.province = province; + this.area = area; + this.street = street; + this.num = num; + } - @Override + @Override public String toString() { return "Address{" + "province='" + province + '\'' + diff --git a/quick-swagger/src/main/java/com/quick/utils/BaseResp.java b/quick-swagger/src/main/java/com/quick/utils/BaseResp.java index 00b95819..6251eacb 100644 --- a/quick-swagger/src/main/java/com/quick/utils/BaseResp.java +++ b/quick-swagger/src/main/java/com/quick/utils/BaseResp.java @@ -1,26 +1,25 @@ package com.quick.utils; -import java.util.Date; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; /** * @param */ +@ApiModel(value = "baseResp") +@JsonInclude(JsonInclude.Include.NON_NULL) public class BaseResp { - /** - * 返回码 - */ + @ApiModelProperty(value = "返回码", required = true) private int code; - /** - * 返回信息描述 - */ + @ApiModelProperty(value = "返回信息描述", required = true) private String message; - /** - * 返回数据 - */ + @ApiModelProperty(value = "返回数据", required = true) private T data; + @ApiModelProperty(value = "系统时间", required = true) private long currentTime; public int getCode() { @@ -39,7 +38,7 @@ public void setMessage(String message) { this.message = message; } - public Object getData() { + public T getData() { return data; } @@ -67,7 +66,7 @@ public BaseResp(int code, String message, T data) { this.code = code; this.message = message; this.data = data; - this.currentTime = new Date().getTime(); + this.currentTime = System.currentTimeMillis(); } /** @@ -78,7 +77,7 @@ public BaseResp(ResultStatus resultStatus) { this.code = resultStatus.getErrorCode(); this.message = resultStatus.getErrorMsg(); this.data = data; - this.currentTime = new Date().getTime(); + this.currentTime = System.currentTimeMillis(); } /** @@ -90,7 +89,7 @@ public BaseResp(ResultStatus resultStatus, T data) { this.code = resultStatus.getErrorCode(); this.message = resultStatus.getErrorMsg(); this.data = data; - this.currentTime = new Date().getTime(); + this.currentTime = System.currentTimeMillis(); } diff --git a/quick-thread/src/main/java/quick/ForkJoinPool/ForkJoinPoolTest.java b/quick-thread/src/main/java/quick/ForkJoinPool/ForkJoinPoolTest.java deleted file mode 100644 index 8efb8a48..00000000 --- a/quick-thread/src/main/java/quick/ForkJoinPool/ForkJoinPoolTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package quick.ForkJoinPool; - -import java.util.Random; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.RecursiveAction; - -/** - * Created with IDEA - * User: vector - * Data: 2017/7/13 - * Time: 17:13 - * Description: - * http://www.infoq.com/cn/articles/fork-join-introduction/ - * https://www.ibm.com/developerworks/cn/java/j-lo-forkjoin/ - */ -public class ForkJoinPoolTest { - /** - * @param args - * @throws Exception - */ - public static void main(String[] args) throws Exception { - // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool - ForkJoinPool forkJoinPool = new ForkJoinPool(); - // 提交可分解的PrintTask任务 - PrintTask printTask = new PrintTask(0, 25); - forkJoinPool.invoke(printTask); -// submit.join(); - -// forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);//阻塞当前线程直到 ForkJoinPool 中所有的任务都执行结束 - // 关闭线程池 - forkJoinPool.shutdown(); - if (printTask.isCompletedNormally()) { - System.out.println("Main: The process has completed normally."); - } - } - -} - -// 无返回值 -class PrintTask extends RecursiveAction { - // 每个"小任务"最多只打印50个数 - private static final int THRESHOLD = 10; - - private int start; - private int end; - - PrintTask(int start, int end) { - this.start = start; - this.end = end; - } - - - @Override - protected void compute() { - // 当end-start的值小于MAX时候,开始打印 - if ((end - start) < THRESHOLD) { - for (int i = start; i < end; i++) { - Random random = new Random(); - try { - Thread.sleep(random.nextInt(1000)); - System.out.println(Thread.currentThread().getName() + "的i值:" + i); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - } - } else { - // 将大任务分解成两个小任务 - int middle = (start + end) / 2; - PrintTask left = new PrintTask(start, middle); - PrintTask right = new PrintTask(middle, end); - // 并行执行两个小任务 -// left.fork(); -// right.fork(); -// left.join(); -// right.join(); - invokeAll(left,right); - } - } -} - diff --git a/quick-thread/src/main/java/quick/ForkJoinPool/ForkJoinPoolTest2.java b/quick-thread/src/main/java/quick/ForkJoinPool/ForkJoinPoolTest2.java deleted file mode 100644 index 138a04ec..00000000 --- a/quick-thread/src/main/java/quick/ForkJoinPool/ForkJoinPoolTest2.java +++ /dev/null @@ -1,98 +0,0 @@ -package quick.ForkJoinPool; - -import java.util.Random; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.Future; -import java.util.concurrent.RecursiveTask; - -/** - * Created with IDEA - * User: vector - * Data: 2017/7/13 - * Time: 17:25 - * Description: - */ -public class ForkJoinPoolTest2 { - /** - * @param args - * @throws Exception - */ - public static void main(String[] args) throws Exception { - int arr[] = new int[10]; - Random random = new Random(); - int total = 0; - // 初始化100个数字元素 - long s1 = System.currentTimeMillis(); - - for (int i = 0; i < arr.length; i++) { -// int temp = random.nextInt(100); - // 对数组元素赋值,并将数组元素的值添加到total总和中 - Thread.sleep(1000); - total += (arr[i] = i); - } - long e1 = System.currentTimeMillis(); - System.out.println("初始化时的总和=" + total + " 耗时:" + (double) (e1 - s1) / 1000); - // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool - ForkJoinPool forkJoinPool = new ForkJoinPool(); - // 提交可分解的PrintTask任务 - - long s2 = System.currentTimeMillis(); - -// Integer invoke = forkJoinPool.invoke(new SumTask(arr, 0, -// arr.length)); - Future future = forkJoinPool.submit(new SumTask(arr, 0, - arr.length)); - long e2 = System.currentTimeMillis(); - - - System.out.println("计算出来的总和=" + future.get() + " 耗时:" + (double) (e1 - s1) / 1000); - // 关闭线程池 - forkJoinPool.shutdown(); - } - -} - -class SumTask extends RecursiveTask { - // 每个"小任务"最多只打印50个数 - private static final int MAX = 20; - private int arr[]; - private int start; - private int end; - - SumTask(int arr[], int start, int end) { - this.arr = arr; - this.start = start; - this.end = end; - } - - @Override - protected Integer compute() { - int sum = 0; - // 当end-start的值小于MAX时候,开始打印 - if ((end - start) < MAX) { - for (int i = start; i < end; i++) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - sum += arr[i]; - } - return sum; - } else { - System.err.println("=====任务分解======"); - // 将大任务分解成两个小任务 - int middle = (start + end) / 2; - SumTask left = new SumTask(arr, start, middle); - SumTask right = new SumTask(arr, middle, end); - // 并行执行两个小任务 - left.fork(); - right.fork(); -// 把两个小任务累加的结果合并起来 - return left.join() + right.join(); - - } - } - -} - diff --git a/quick-thread/src/main/java/quick/ForkJoinPool/ForkJoinQuickSort.java b/quick-thread/src/main/java/quick/ForkJoinPool/ForkJoinQuickSort.java deleted file mode 100644 index 120504df..00000000 --- a/quick-thread/src/main/java/quick/ForkJoinPool/ForkJoinQuickSort.java +++ /dev/null @@ -1,60 +0,0 @@ -package quick.ForkJoinPool; - -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.RecursiveAction; - -public class ForkJoinQuickSort extends RecursiveAction { - - public static void main(String [] args){ - int arr [] = {5,6,9,2,5,4,7}; - ForkJoinQuickSort fjQs = new ForkJoinQuickSort(arr,0,arr.length); - ForkJoinPool fjpool = new ForkJoinPool(); - fjpool.invoke(fjQs); - for(int i:arr){ - System.out.println(i); - } - } - private int [] m_ElementsTobeSorted; - private int m_StartIndex; - private int m_EndIndex; - public ForkJoinQuickSort(int [] elements , int startIndex , int endIndex){ - m_ElementsTobeSorted=elements; - m_StartIndex=startIndex; - m_EndIndex = endIndex; - } - public void compute(){ - sort(m_ElementsTobeSorted,m_StartIndex,m_EndIndex); - } - public int[] getSortedElements(){ - return m_ElementsTobeSorted; - } - private void sort(int arr[],int startlength,int endLength){ - if( (endLength-startlength==1)||(endLength-startlength==0)) - return ; - else{ - int pivot = arr[endLength-1]; - int pivotIndex = endLength-1; - for(int i=startlength;i{ -// tasks.parallelStream().forEach(t->{ -// try { -// String gdsstatus=transactionService.GetTransInfo(url, t.getTask_id()); -// checkStatus(t.getTask_id(),t.getTask_status(),gdsstatus); -// } catch (Exception e) { -// System.out.println("EXCEPTION OCCOR IN TASK:"+t.getTask_id()); -// e.printStackTrace(); -// } -// -// System.out.println("NO:"+count.getAndIncrement()+" is done"); -// -// }); -// }); -// } -//} diff --git a/quick-thread/src/main/java/quick/Stealing.java b/quick-thread/src/main/java/quick/Stealing.java deleted file mode 100644 index 1852c16c..00000000 --- a/quick-thread/src/main/java/quick/Stealing.java +++ /dev/null @@ -1,172 +0,0 @@ -package quick; - -import java.util.Arrays; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.RecursiveAction; -import java.util.concurrent.TimeUnit; - -public class Stealing { - - private final static int NTHREADS = Runtime.getRuntime().availableProcessors(); - - private void work(double[] array, int from, int to) { - for (int j = from; j < to; j++) { - array[j] = Math.log(j); - } - } - - public void attempt1(final double[] array) { - Thread[] threads = new Thread[NTHREADS - 1]; - final int segmentLen = array.length / NTHREADS; - int offset = 0; - for (int i = 0; i < NTHREADS - 1; i++) { - final int from = offset; - final int to = offset + segmentLen; - threads[i] = new Thread(new Runnable() { - - @Override - public void run() { - work(array, from, to); - } - }); - threads[i].start(); - offset += segmentLen; - } - // do last segment on main thread - work(array, array.length - segmentLen, array.length); - - // wait for completion - for (int i = 0; i < NTHREADS - 1; i++) { - try { - threads[i].join(); - } catch (InterruptedException ignore) { - } - } - } - - public void attempt2(final double[] array) { - ExecutorService exec = Executors.newFixedThreadPool(NTHREADS - 1); - final int segmentLen = array.length / NTHREADS; - int offset = 0; - for (int i = 0; i < NTHREADS - 1; i++) { - final int from = offset; - final int to = offset + segmentLen; - exec.execute(new Runnable() { - - @Override - public void run() { - work(array, from, to); - } - }); - offset += segmentLen; - } - // do last segment on main thread - work(array, array.length - segmentLen, array.length); - - // wait for completion - exec.shutdown(); - try { - exec.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException ignore) { - } - } - - class ForEach extends RecursiveAction { - - private double[] array; - private int from; - private int to; - - // you can fine-tune this, - // should be sth between 100 and 10000 - public final static int TASK_LEN = 5000; - - public ForEach(double[] array, int from, int to) { - this.array = array; - this.from = from; - this.to = to; - } - - @Override - protected void compute() { - int len = to - from; - if (len < TASK_LEN) { - work(array, from, to); - } else { - // split work in half, execute sub-tasks asynchronously - int mid = (from + to) >>> 1; - new ForEach(array, from, mid).fork(); - new ForEach(array, mid, to).fork(); - } - } - } - - public void attempt3(final double[] array) { - ForkJoinPool pool = new ForkJoinPool(NTHREADS); - // blocks until completion - pool.invoke(new ForEach(array, 0, array.length)); - } - - public void test() { - final int ROUNDS = 10; - long seq = 0, a1 = 0, a2 = 0, a3 = 0, t; - - double[] array = new double[8388608]; - - for (int i = 0; i < ROUNDS; i++) { - t = System.currentTimeMillis(); - work(array, 0, array.length); - seq += System.currentTimeMillis() - t; - } - seq /= ROUNDS; - print(array); - - clear(array); - for (int i = 0; i < ROUNDS; i++) { - t = System.currentTimeMillis(); - attempt1(array); - a1 += System.currentTimeMillis() - t; - } - a1 /= ROUNDS; - print(array); - - clear(array); - for (int i = 0; i < ROUNDS; i++) { - t = System.currentTimeMillis(); - attempt2(array); - a2 += System.currentTimeMillis() - t; - } - a2 /= ROUNDS; - print(array); - - clear(array); - for (int i = 0; i < ROUNDS; i++) { - t = System.currentTimeMillis(); - attempt3(array); - a3 += System.currentTimeMillis() - t; - } - a3 /= ROUNDS; - print(array); - - System.out.println("sequential avg: " + seq + " ms"); - System.out.println("attempt 1 avg: " + a1 + " ms"); - System.out.println("attempt 2 avg: " + a2 + " ms"); - System.out.println("attempt 3 avg: " + a3 + " ms"); - } - - private void clear(double[] array) { - for (int i = 0; i < array.length; i++) { - array[i] = 0; - } - } - - private void print(double[] array) { - System.out.println(Arrays.toString(Arrays.copyOfRange(array, 0, 10))); - } - - public static void main(String[] args) { - new Stealing().test(); - } -} \ No newline at end of file diff --git a/quick-thread/src/main/java/quick/executeservice/CacheThreadPool.java b/quick-thread/src/main/java/quick/executeservice/CacheThreadPool.java deleted file mode 100644 index b381172a..00000000 --- a/quick-thread/src/main/java/quick/executeservice/CacheThreadPool.java +++ /dev/null @@ -1,34 +0,0 @@ -package quick.executeservice; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * Created by wangxc on 2017/3/27. - */ -public class CacheThreadPool { - public static void main(String[] args) { - useThread(); - notUseThread(); - } - - private static void notUseThread() { - for (int i = 0; i < 15; i++) { - System.out.println("获取" + i + "的信息并保存"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - private static void useThread() { - ExecutorService exec = Executors.newCachedThreadPool(); - for (int i = 0; i < 15; i++) - exec.execute(new MyThread("张" + i)); - exec.shutdown();//并不是终止线程的运行,而是禁止在这个Executor中添加新的任务 - } -} - - diff --git a/quick-thread/src/main/java/quick/executeservice/ExecutorServiceDemo.java b/quick-thread/src/main/java/quick/executeservice/ExecutorServiceDemo.java deleted file mode 100644 index bea41360..00000000 --- a/quick-thread/src/main/java/quick/executeservice/ExecutorServiceDemo.java +++ /dev/null @@ -1,41 +0,0 @@ -package quick.executeservice; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -/** - * Created by wangxc on 2017/3/29. - */ -public class ExecutorServiceDemo { - - - /** - * 使用Executor的invokeAll方法,同时执行 - * @param args - * @throws InterruptedException - * @throws ExecutionException - */ - public static void main(String[] args) throws InterruptedException, ExecutionException { - - - long stat = System.currentTimeMillis(); - ExecutorService executorService = Executors.newCachedThreadPool(); - List callList = new ArrayList(); - for(int i=0;i<10000;i++){ - callList.add(new TaskSleep(i)); - } - List> futures = executorService.invokeAll(callList); - executorService.shutdown(); - int sum=0; - for(Future item:futures){ - sum += item.get(); - } - System.out.println("结果"+sum); - long end = System.currentTimeMillis(); - System.out.println((double)(end-stat)/1000); - } -} diff --git a/quick-thread/src/main/java/quick/executeservice/MyThread.java b/quick-thread/src/main/java/quick/executeservice/MyThread.java deleted file mode 100644 index d4eefdbb..00000000 --- a/quick-thread/src/main/java/quick/executeservice/MyThread.java +++ /dev/null @@ -1,20 +0,0 @@ -package quick.executeservice; - -class MyThread extends Thread { - - private String name; - - public MyThread(String name) { - this.name = name; - } - - @Override - public void run() { - System.out.println("获取"+name+"的信息并保存"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } -} \ No newline at end of file diff --git a/quick-thread/src/main/java/quick/executeservice/TaskSleep.java b/quick-thread/src/main/java/quick/executeservice/TaskSleep.java deleted file mode 100644 index 67219f4a..00000000 --- a/quick-thread/src/main/java/quick/executeservice/TaskSleep.java +++ /dev/null @@ -1,22 +0,0 @@ -package quick.executeservice; - -import java.util.concurrent.Callable; - -/** - * Created by wangxc on 2017/3/29. - */ -public class TaskSleep implements Callable { - - private int num; - - public TaskSleep(int num){ - this.num = num; - } - - public Integer call() throws Exception { -// System.out.println(num + "--->" +i); - System.out.println(Thread.currentThread().getName()+"--->执行中"); - Thread.sleep(10); - return num; - } -} diff --git a/quick-tika/src/main/java/com/quick/Application.java b/quick-tika/src/main/java/com/quick/Application.java deleted file mode 100644 index 71bf572d..00000000 --- a/quick-tika/src/main/java/com/quick/Application.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.quick; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; - -/** - * Created by wangxc on 2017/3/9. - */ -@SpringBootApplication -@EnableSwagger2 -public class Application { - - @Bean - public Docket createRestApi() { - return new Docket(DocumentationType.SWAGGER_2) - .apiInfo(apiInfo()) - .select() - .apis(RequestHandlerSelectors.basePackage("com.quick.controller")) - .paths(PathSelectors.any()) - .build(); - } - private ApiInfo apiInfo() { - return new ApiInfoBuilder() - .title("抽取文件文本内容,没有类型限制") - .description("对一些文本文件进行内容的抽取,目前米有文件的限制,也没有大小的限制,但是可能会有预料之外的结果") - .termsOfServiceUrl("http://vector4wang.tk") - .contact("Vector.Wang") - .version("1.0") - .build(); - } - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} diff --git a/quick-tika/src/main/java/com/quick/controller/WebController.java b/quick-tika/src/main/java/com/quick/controller/WebController.java deleted file mode 100644 index ff8a7275..00000000 --- a/quick-tika/src/main/java/com/quick/controller/WebController.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.quick.controller; - -import com.quick.tika.TikaUtil; -import com.quick.util.BaseResp; -import com.quick.util.ResultStatus; -import com.quick.util.StringUtils; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import org.apache.commons.io.IOUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -/** - * Created with IDEA - * User: vector - * Data: 2017/4/18 - * Time: 10:14 - * Description: - */ -@RestController -@RequestMapping("/web") -@Api(value = "Tika本地服务接口", description = "抽取文件的文本内容") -public class WebController { - - - @ApiOperation("上传文本,返回文本内容") - @ApiResponses({ - @ApiResponse(code = 400, message = "请求参数没填好"), - @ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对") - }) - @RequestMapping(value = "/content", method = RequestMethod.POST) - public - @ResponseBody - BaseResp getFileContent(@RequestParam("file") MultipartFile file) { - if (!file.isEmpty()) { - try { - String fileContent = TikaUtil.handleStreamContent(file.getBytes()); - return new BaseResp(ResultStatus.SUCCESS, fileContent); - } catch (Exception e) { - e.printStackTrace(); - return new BaseResp(ResultStatus.error_file_upload, "You failed to upload => " + e.getCause()); - } - } else { - return new BaseResp(ResultStatus.error_file_upload, "You failed to upload because the file was empty."); - } - } - - @ApiOperation("上传文本,返回文本MetaData信息") - @ApiResponses({ - @ApiResponse(code = 400, message = "请求参数没填好"), - @ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对") - }) - @RequestMapping(value = "/metaData", method = RequestMethod.POST) - public - @ResponseBody - BaseResp> getFileMetaData(@RequestParam("file") MultipartFile file) { - Map fileMetadata = new HashMap<>(); - if (!file.isEmpty()) { - try { - fileMetadata = TikaUtil.handleStreamMetaDate(file.getBytes()); - return new BaseResp>(ResultStatus.SUCCESS, fileMetadata); - } catch (Exception e) { - fileMetadata.put("cause", "You failed to upload => " + e.getCause()); - return new BaseResp>(ResultStatus.error_file_upload, fileMetadata); - } - } else { - fileMetadata.put("cause", "You failed to upload => file is empty"); - return new BaseResp>(ResultStatus.error_file_upload, fileMetadata); - } - } - -} diff --git a/quick-tika/src/main/java/com/quick/tika/TikaUtil.java b/quick-tika/src/main/java/com/quick/tika/TikaUtil.java deleted file mode 100644 index b96a6cf6..00000000 --- a/quick-tika/src/main/java/com/quick/tika/TikaUtil.java +++ /dev/null @@ -1,248 +0,0 @@ -package com.quick.tika; - -import org.apache.commons.io.IOUtils; -import org.apache.tika.exception.TikaException; -import org.apache.tika.extractor.DocumentSelector; -import org.apache.tika.io.TikaInputStream; -import org.apache.tika.metadata.Metadata; -import org.apache.tika.mime.MediaType; -import org.apache.tika.parser.AbstractParser; -import org.apache.tika.parser.AutoDetectParser; -import org.apache.tika.parser.ParseContext; -import org.apache.tika.parser.Parser; -import org.apache.tika.parser.html.BoilerpipeContentHandler; -import org.apache.tika.sax.BodyContentHandler; -import org.apache.tika.sax.ContentHandlerDecorator; -import org.apache.tika.sax.TeeContentHandler; -import org.apache.tika.sax.XHTMLContentHandler; -import org.xml.sax.Attributes; -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.AttributesImpl; - -import javax.xml.transform.OutputKeys; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.sax.SAXTransformerFactory; -import javax.xml.transform.sax.TransformerHandler; -import javax.xml.transform.stream.StreamResult; -import java.io.*; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public class TikaUtil { - - - /** - * Parsing context. - */ - private static ParseContext context; - - /** - * Configured parser instance. - */ - private static Parser parser; - - /** - * Captures requested embedded images - */ - private static ImageSavingParser imageParser; - - /** - * init method after server complete - */ - static { - context = new ParseContext(); - parser = new AutoDetectParser(); - imageParser = new ImageSavingParser(parser); - context.set(DocumentSelector.class, new ImageDocumentSelector()); - context.set(Parser.class, imageParser); - } - - public static String handleStreamContent(byte[] file) - throws Exception { - Metadata md = new Metadata(); - TikaInputStream input = TikaInputStream.get(file, md); - StringWriter textBuffer = new StringWriter(); - StringBuilder metadataBuffer = new StringBuilder(); - - ContentHandler handler = new TeeContentHandler( - getTextContentHandler(textBuffer) - ); - parser.parse(input, handler, md, context); - return textBuffer.toString(); - } - - public static Map handleStreamMetaDate(byte[] file) - throws Exception { - Map meta = new HashMap<>(); - Metadata md = new Metadata(); - TikaInputStream input = TikaInputStream.get(file, md); - StringWriter textBuffer = new StringWriter(); - - ContentHandler handler = new TeeContentHandler( - getTextContentHandler(textBuffer) - ); - parser.parse(input, handler, md, context); - - String[] names = md.names(); - Arrays.sort(names); - for (String name : names) { - meta.put(name, md.get(name)); - } - return meta; - } - - private static ContentHandler getHtmlHandler(Writer writer) - throws TransformerConfigurationException { - SAXTransformerFactory factory = (SAXTransformerFactory) - SAXTransformerFactory.newInstance(); - TransformerHandler handler = factory.newTransformerHandler(); - handler.getTransformer().setOutputProperty(OutputKeys.METHOD, "html"); - handler.setResult(new StreamResult(writer)); - return new ContentHandlerDecorator(handler) { - @Override - public void startElement( - String uri, String localName, String name, Attributes atts) - throws SAXException { - if (XHTMLContentHandler.XHTML.equals(uri)) { - uri = null; - } - if (!"head".equals(localName)) { - if ("img".equals(localName)) { - AttributesImpl newAttrs; - if (atts instanceof AttributesImpl) { - newAttrs = (AttributesImpl) atts; - } else { - newAttrs = new AttributesImpl(atts); - } - - for (int i = 0; i < newAttrs.getLength(); i++) { - if ("src".equals(newAttrs.getLocalName(i))) { - String src = newAttrs.getValue(i); - if (src.startsWith("embedded:")) { - String filename = src.substring(src.indexOf(':') + 1); - try { - File img = imageParser.requestSave(filename); - String newSrc = img.toURI().toString(); - newAttrs.setValue(i, newSrc); - } catch (IOException e) { - System.err.println("Error creating temp image file " + filename); - // The html viewer will show a broken image too to alert them - } - } - } - } - super.startElement(uri, localName, name, newAttrs); - } else { - super.startElement(uri, localName, name, atts); - } - } - } - - @Override - public void endElement(String uri, String localName, String name) - throws SAXException { - if (XHTMLContentHandler.XHTML.equals(uri)) { - uri = null; - } - if (!"head".equals(localName)) { - super.endElement(uri, localName, name); - } - } - - @Override - public void startPrefixMapping(String prefix, String uri) { - } - - @Override - public void endPrefixMapping(String prefix) { - } - }; - } - - private static ContentHandler getTextContentHandler(Writer writer) { - return new BodyContentHandler(writer); - } - - private static ContentHandler getTextMainContentHandler(Writer writer) { - return new BoilerpipeContentHandler(writer); - } - - private static ContentHandler getXmlContentHandler(Writer writer) - throws TransformerConfigurationException { - SAXTransformerFactory factory = (SAXTransformerFactory) - SAXTransformerFactory.newInstance(); - TransformerHandler handler = factory.newTransformerHandler(); - handler.getTransformer().setOutputProperty(OutputKeys.METHOD, "xml"); - handler.setResult(new StreamResult(writer)); - return handler; - } - - /** - * A {@link DocumentSelector} that accepts only images. - */ - private static class ImageDocumentSelector implements DocumentSelector { - public boolean select(Metadata metadata) { - String type = metadata.get(Metadata.CONTENT_TYPE); - return type != null && type.startsWith("image/"); - } - } - - /** - * A recursive parser that saves certain images into the temporary - * directory, and delegates everything else to another downstream - * parser. - */ - private static class ImageSavingParser extends AbstractParser { - private Map wanted = new HashMap(); - private Parser downstreamParser; - private File tmpDir; - - private ImageSavingParser(Parser downstreamParser) { - this.downstreamParser = downstreamParser; - - try { - File t = File.createTempFile("tika", ".test"); - tmpDir = t.getParentFile(); - } catch (IOException e) { - } - } - - public File requestSave(String embeddedName) throws IOException { - String suffix = ".tika"; - - int splitAt = embeddedName.lastIndexOf('.'); - if (splitAt > 0) { - embeddedName.substring(splitAt); - } - - File tmp = File.createTempFile("tika-embedded-", suffix); - wanted.put(embeddedName, tmp); - return tmp; - } - - public Set getSupportedTypes(ParseContext context) { - // Never used in an auto setup - return null; - } - - public void parse(InputStream stream, ContentHandler handler, - Metadata metadata, ParseContext context) throws IOException, - SAXException, TikaException { - String name = metadata.get(Metadata.RESOURCE_NAME_KEY); - if (name != null && wanted.containsKey(name)) { - FileOutputStream out = new FileOutputStream(wanted.get(name)); - IOUtils.copy(stream, out); - out.close(); - } else { - if (downstreamParser != null) { - downstreamParser.parse(stream, handler, metadata, context); - } - } - } - - } - -} diff --git a/quick-tika/src/main/java/com/quick/util/BaseResp.java b/quick-tika/src/main/java/com/quick/util/BaseResp.java deleted file mode 100644 index 1c2e9817..00000000 --- a/quick-tika/src/main/java/com/quick/util/BaseResp.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.quick.util; - -import java.util.Date; - -/** - * @param - */ -public class BaseResp { - /** - * 返回码 - */ - private int code; - - /** - * 返回信息描述 - */ - private String message; - - /** - * 返回数据 - */ - private T data; - - private long currentTime; - - public int getCode() { - return code; - } - - public void setCode(int code) { - this.code = code; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public Object getData() { - return data; - } - - public void setData(T data) { - this.data = data; - } - - public long getCurrentTime() { - return currentTime; - } - - public void setCurrentTime(long currentTime) { - this.currentTime = currentTime; - } - - public BaseResp(){} - - /** - * - * @param code 错误码 - * @param message 信息 - * @param data 数据 - */ - public BaseResp(int code, String message, T data) { - this.code = code; - this.message = message; - this.data = data; - this.currentTime = new Date().getTime(); - } - - /** - * 不带数据的返回结果 - * @param resultStatus - */ - public BaseResp(ResultStatus resultStatus) { - this.code = resultStatus.getErrorCode(); - this.message = resultStatus.getErrorMsg(); - this.data = data; - this.currentTime = new Date().getTime(); - } - - /** - * 带数据的返回结果 - * @param resultStatus - * @param data - */ - public BaseResp(ResultStatus resultStatus, T data) { - this.code = resultStatus.getErrorCode(); - this.message = resultStatus.getErrorMsg(); - this.data = data; - this.currentTime = new Date().getTime(); - } - - -} \ No newline at end of file diff --git a/quick-tika/src/main/java/com/quick/util/StringUtils.java b/quick-tika/src/main/java/com/quick/util/StringUtils.java deleted file mode 100644 index f5580568..00000000 --- a/quick-tika/src/main/java/com/quick/util/StringUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.quick.util; - -import java.math.BigInteger; -import java.security.MessageDigest; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Created by vector on 2017/3/21. - */ -public class StringUtils { - - private static final String regEx_script = "]*?>[\\s\\S]*?<\\/script>"; // 定义script的正则表达式 - private static final String regEx_style = "]*?>[\\s\\S]*?<\\/style>"; // 定义style的正则表达式 - private static final String regEx_html = "<[^>]+>"; // 定义HTML标签的正则表达式 - private static final String regEx_space = "\\s*|\t|\r|\n";//定义空格回车换行符 - - - public static String getMD5(String str) throws Exception { - try { - // 生成一个MD5加密计算摘要 - MessageDigest md = MessageDigest.getInstance("MD5"); - // 计算md5函数 - md.update(str.getBytes()); - // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符 - // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值 - return new BigInteger(1, md.digest()).toString(16); - } catch (Exception e) { - throw new Exception("MD5加密出现错误"); - } - } - - /** - * 去除字符串里的html标签 - * @param htmlStr - * @return - */ - public static String delHTMLTag(String htmlStr){ - Pattern p_script = Pattern.compile(regEx_script, Pattern.CASE_INSENSITIVE); - Matcher m_script = p_script.matcher(htmlStr); - htmlStr = m_script.replaceAll(""); // 过滤script标签 - - Pattern p_style = Pattern.compile(regEx_style, Pattern.CASE_INSENSITIVE); - Matcher m_style = p_style.matcher(htmlStr); - htmlStr = m_style.replaceAll(""); // 过滤style标签 - - Pattern p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE); - Matcher m_html = p_html.matcher(htmlStr); - htmlStr = m_html.replaceAll(""); // 过滤html标签 - - Pattern p_space = Pattern.compile(regEx_space, Pattern.CASE_INSENSITIVE); - Matcher m_space = p_space.matcher(htmlStr); - htmlStr = m_space.replaceAll(""); // 过滤空格回车标签 - return htmlStr.trim(); // 返回文本字符串 - } - - public static String getTextFromHtml(String htmlStr){ - htmlStr = delHTMLTag(htmlStr); - htmlStr = htmlStr.replaceAll(" ", ""); - return htmlStr; - } -} diff --git a/quick-tika/src/main/resources/application.properties b/quick-tika/src/main/resources/application.properties deleted file mode 100644 index f762ae3e..00000000 --- a/quick-tika/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -## ö˿ں -server.port=9090 - diff --git a/quick-undertow/pom.xml b/quick-undertow/pom.xml new file mode 100644 index 00000000..ae7ed484 --- /dev/null +++ b/quick-undertow/pom.xml @@ -0,0 +1,42 @@ + + + quick-undertow + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + + org.springframework.boot + spring-boot-starter-parent + 2.1.6.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-undertow + + + + \ No newline at end of file diff --git a/quick-undertow/src/main/java/com/quick/undertow/Application.java b/quick-undertow/src/main/java/com/quick/undertow/Application.java new file mode 100644 index 00000000..ef584ec2 --- /dev/null +++ b/quick-undertow/src/main/java/com/quick/undertow/Application.java @@ -0,0 +1,17 @@ +package com.quick.undertow; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + * @author wangxc + * @date: 2020/2/24 下午5:45 + * + */ +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class); + } +} diff --git a/quick-undertow/src/main/resources/application.properties b/quick-undertow/src/main/resources/application.properties new file mode 100644 index 00000000..e542da5c --- /dev/null +++ b/quick-undertow/src/main/resources/application.properties @@ -0,0 +1,23 @@ +# Undertow 日志存放目录 +server.undertow.accesslog.dir= + # 是否启动日志 +server.undertow.accesslog.enabled=false + # 日志格式 +server.undertow.accesslog.pattern=common + # 日志文件名前缀 +server.undertow.accesslog.prefix=access_log + # 日志文件名后缀 +server.undertow.accesslog.suffix=log + # HTTP POST请求最大的大小 +server.undertow.max-http-post-size=0 + # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程 +server.undertow.io-threads=4 + # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载 +server.undertow.worker-threads=20 + # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理 + # 每块buffer的空间大小,越小的空间被利用越充分 +server.undertow.buffer-size=1024 + # 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region +server.undertow.buffers-per-region=1024 + # 是否分配的直接内存 +server.undertow.direct-buffers=true diff --git a/quick-vue/pom.xml b/quick-vue/pom.xml new file mode 100644 index 00000000..983a59e4 --- /dev/null +++ b/quick-vue/pom.xml @@ -0,0 +1,28 @@ + + + quick-vue + 1.0-SNAPSHOT + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter + + + + + \ No newline at end of file diff --git a/quick-vue/src/main/java/com/vue/VueApplication.java b/quick-vue/src/main/java/com/vue/VueApplication.java new file mode 100644 index 00000000..f4b48ff8 --- /dev/null +++ b/quick-vue/src/main/java/com/vue/VueApplication.java @@ -0,0 +1,18 @@ +package com.vue; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Created with IDEA + * User: vector + * Data: 2018/2/11 0011 + * Time: 17:20 + * Description: + */ +@SpringBootApplication +public class VueApplication { + public static void main(String[] args) { + SpringApplication.run(VueApplication.class); + } +} diff --git a/quick-vue/src/main/java/com/vue/controller/ApiController.java b/quick-vue/src/main/java/com/vue/controller/ApiController.java new file mode 100644 index 00000000..cd5a88cf --- /dev/null +++ b/quick-vue/src/main/java/com/vue/controller/ApiController.java @@ -0,0 +1,20 @@ +package com.vue.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Created with IDEA + * User: vector + * Data: 2018/2/11 0011 + * Time: 17:20 + * Description: + */ +@RestController +public class ApiController { + + @RequestMapping("/hello") + public String hello() { + return "Hello Quick SpringBoot-Vue"; + } +} diff --git a/quick-vue/src/main/resources/application.properties b/quick-vue/src/main/resources/application.properties new file mode 100644 index 00000000..9a0c7894 --- /dev/null +++ b/quick-vue/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port = 8000 \ No newline at end of file diff --git a/quick-vue/src/main/resources/banner.txt b/quick-vue/src/main/resources/banner.txt new file mode 100644 index 00000000..49d82d5c --- /dev/null +++ b/quick-vue/src/main/resources/banner.txt @@ -0,0 +1,15 @@ + . ____ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ + ( ( )\___ | '_ | '_| | '_ \/ _` | + \\/ ___)| |_)| | | | | || (_| | + ' |____| .__|_| |_|_| |_\__, | +\ ===========|_|==============|___/== ▀ +\- ▌ SpringBoot-vue ▀ + - ▌ (o) ▀ +/- ▌ Go Go Go ! ▀ +/ =================================== ▀ + ██ + +${AnsiColor.BRIGHT_RED}Spring Boot Version : ${spring-boot.version} ${spring-boot.formatted-version} +${AnsiColor.BRIGHT_CYAN}SpringBoot-Vue.js : 0.0.1.RELEASE +${AnsiColor.BRIGHT_BLACK}Author : Boyle Gu diff --git a/quick-vw-crawler/README.md b/quick-vw-crawler/README.md new file mode 100644 index 00000000..1e09d39c --- /dev/null +++ b/quick-vw-crawler/README.md @@ -0,0 +1,207 @@ +# 使用springboot+vw-crawler轻松抓取CSDN的文章 + +有关VW-Cralwer的介绍可以看[这里](https://github.com/vector4wang/vw-crawler),简单轻便开源的一款Java爬虫框架。 + +下面结合比较流行的框架SpringBoot抓取CSDN的数据(有关的Spingboot的使用可以参考[这里](https://github.com/vector4wang/spring-boot-quick)) + + +## 配置POM +使用Springboot做架构,redis做数据存储,vw-crawler做爬虫模块,最终的pom如下 +``` + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + UTF-8 + UTF-8 + 1.8 + 0.0.4 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + com.github.vector4wang + vw-crawler + 0.0.5 + + + org.springframework.boot + spring-boot-starter-redis + + + com.alibaba + fastjson + 1.2.7 + + +``` +## redis相关配置 +因为已经添加了redis的相关依赖,只需要在application.properties里配置redis的链接参数即可,如下 +``` +# Redis数据库索引(默认为0) +spring.redis.database=1 +# Redis服务器地址 +spring.redis.host=localhost +# Redis服务器连接端口 +spring.redis.port=6379 +# Redis服务器连接密码(默认为空) +spring.redis.password= +# 连接池最大连接数(使用负值表示没有限制) +spring.redis.pool.max-active=8 +# 连接池最大阻塞等待时间(使用负值表示没有限制) +spring.redis.pool.max-wait=-1 +# 连接池中的最大空闲连接 +spring.redis.pool.max-idle=8 +# 连接池中的最小空闲连接 +spring.redis.pool.min-idle=0 +# 连接超时时间(毫秒) +spring.redis.timeout=0 +``` +代码里的使用 +``` +@Component +public class DataCache { + @Autowired + private StringRedisTemplate redisTemplate; + public void save(Blog blog) { + redisTemplate.opsForValue().set(blog.getUrlMd5(), JSON.toJSONString(blog)); + } + public Blog get(String url) { + String md5Url = Md5Util.getMD5(url.getBytes()); + String blogStr = redisTemplate.opsForValue().get(md5Url); + if (StringUtils.isEmpty(blogStr)) { + return new Blog(); + } + return JSON.parseObject(blogStr, Blog.class); + } +} +``` +比较简单,一个保存一个获取即可,这里使用StringRedisTemplate时为了更直观的在redis客户端中查看内容。 + + +## 爬虫 + +### 页面数据model +使用流行的注解方式来填充数据 +``` +@CssSelector(selector = "#mainBox > main > div.blog-content-box > div.article-title-box > h1", resultType = SelectType.TEXT) +private String title; + +@CssSelector(selector = "#mainBox > main > div.blog-content-box > div.article-info-box > div > span.time", dateFormat = "yyyy年MM月dd日 HH:mm:ss") +private Date publishDate; + +@CssSelector(selector = "#mainBox > main > div.blog-content-box > div.article-info-box > div > div > span", resultType = SelectType.TEXT) +private String readCountStr; + +private int readCount; + +@CssSelector(selector = "#article_content",resultType = SelectType.TEXT) +private String content; + +@CssSelector(selector = "body > div.tool-box > ul > li:nth-child(1) > button > p",resultType = SelectType.TEXT) +private int likeCount; + +/** + * 暂时不支持自动解析列表的功能,所以加个中间变量,需要二次解析下 + */ +@CssSelector(selector = "#mainBox > main > div.comment-box > div.comment-list-container > div.comment-list-box",resultType = SelectType.HTML) +private String comentTmp; + +private String url; + +private String urlMd5; + +private List comment; +``` +使用选择器来精确定位数据,使用chrome浏览器的可以这样快速获取 +[![WX20180722-103513@2x.png](https://i.loli.net/2018/07/22/5b53edce666f9.png)](https://i.loli.net/2018/07/22/5b53edce666f9.png) +如图,也支持xpath的快捷选择(vw-crawler后续会支持xpath定位元素),当然了,有些元素如"阅读数:xxx",是不能自动转化为整型,所以还需要第二次解析处理 + +### 爬虫配置 +这里需要配置请求头如“User-Agent”,目标页URL的正则表达式,列表页URL的正则,还有爬虫的线程数和超时等等,如下 +``` +new VWCrawler.Builder().setUrl("https://blog.csdn.net/qqhjqs").setHeader("User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36") + .setTargetUrlRex("https://blog.csdn.net/qqhjqs/article/details/[0-9]+").setThreadCount(5) + .setTimeOut(5000).setPageParser(new CrawlerService() { + @Override + public void parsePage(Document doc, Blog pageObj) { + pageObj.setReadCount(Integer.parseInt(pageObj.getReadCountStr().replace("阅读数:", ""))); + pageObj.setUrl(doc.baseUri()); + pageObj.setUrlMd5(Md5Util.getMD5(pageObj.getUrl().getBytes())); + } + @Override + public void save(Blog pageObj) { + dataCache.save(pageObj); + } + }).build().start(); +``` +需要实现CrawlerService,看下源码 +``` +public boolean isExist(String url){ + return false; +} +public boolean isContinue(Document document){ + if (document == null) { + return false; + } + return true; +} + +public abstract void parsePage(Document doc, T pageObj); + +public abstract void save(T pageObj); +``` + +可以看到parsePage可以处理数据的二次解析,save则负责保存数据,isExist和isContinue是处理爬取过程中的一些判断逻辑 + +- isExist 是url的二次判重,你可以通过查找数据库来判断即将要抓取的URL是否存在数据库中,存在则返回true,不存则正常抓取即返回false; +- isContinue 是在解析页面数据前判断一下该页面是否有WAF(Web Application Firewal),如果有则返回false,没有则正常进行解析即返回true; + +要在springboot全部初始化完毕之后再去启动爬虫, 所有需要这样配置 +``` +@Component +@Order +public class Crawler implements CommandLineRunner { + @Autowired + private DataCache dataCache; + @Override + public void run(String... strings) { + // 爬虫配置 + } +} +``` + + +### 启动 +直接右键执行CrawlerApplication即可 +最终抓取如图 +[![WX20180722-110714@2x.png](https://i.loli.net/2018/07/22/5b53f4e2a8b00.png)](https://i.loli.net/2018/07/22/5b53f4e2a8b00.png) + + + +## 相关链接 + +[VW-Crawler](https://github.com/vector4wang/vw-crawler) [SpringBoot](https://github.com/vector4wang/spring-boot-quick) + +该爬虫地址:[传送门](https://github.com/vector4wang/spring-boot-quick/tree/master/quick-vw-crawler) + +2018年08月13日20:40:38 更新 + + + + + diff --git a/quick-vw-crawler/pom.xml b/quick-vw-crawler/pom.xml new file mode 100644 index 00000000..2cd8e2eb --- /dev/null +++ b/quick-vw-crawler/pom.xml @@ -0,0 +1,50 @@ + + + quick-vwcrawler + 0.0.6-SNAPSHOTS + 4.0.0 + + + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml + + + + UTF-8 + UTF-8 + 1.8 + 0.0.4 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + com.github.vector4wang + vw-crawler + 0.0.5 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.alibaba + fastjson + + + + \ No newline at end of file diff --git a/quick-vw-crawler/src/main/java/com/crawler/vw/CrawlerApplication.java b/quick-vw-crawler/src/main/java/com/crawler/vw/CrawlerApplication.java new file mode 100644 index 00000000..6d928520 --- /dev/null +++ b/quick-vw-crawler/src/main/java/com/crawler/vw/CrawlerApplication.java @@ -0,0 +1,11 @@ +package com.crawler.vw; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CrawlerApplication { + public static void main(String[] args) { + SpringApplication.run(CrawlerApplication.class, args); + } +} diff --git a/quick-vw-crawler/src/main/java/com/crawler/vw/TestMain.java b/quick-vw-crawler/src/main/java/com/crawler/vw/TestMain.java new file mode 100644 index 00000000..7a12f192 --- /dev/null +++ b/quick-vw-crawler/src/main/java/com/crawler/vw/TestMain.java @@ -0,0 +1,51 @@ +package com.crawler.vw; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author vector + * @Data 2018/8/15 0015 + * @Description TODO + */ +public class TestMain { + public static void main(String[] args) throws IOException { + Map headers = new HashMap<>(); + headers.put("User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36"); + headers.put("Cookie", + "yunsuo_session_verify=1a3375389647fba22b7902b9fb4ace23; UM_distinctid=1653ca373b49dd-08590abd5d5966-e323462-1fa400-1653ca373b5d45; CNZZDATA1261546263=791873602-1534320347-%7C1534320347; JSESSIONID=BBAEDA8A8DED1B76BF8B5F576B52FFDD; PD=9ebee55477eac1bcc49b735f29d4f4134928223a2c77d03ba86aebe3044aa69c698511426b80c7d7"); + headers.put("Referer", + "https://www.patexplorer.com/results/s.html?sc=&q=%E6%AD%A6%E6%B1%89%E7%91%9E%E5%8D%9A%E7%A7%91%E5%88%9B%E7%94%9F%E7%89%A9%E6%8A%80%E6%9C%AF%E6%9C%89%E9%99%90%E5%85%AC%E5%8F%B8&fq=&type=s&sort=&sortField="); + headers.put("Host", "www.patexplorer.com"); + headers.put("Origin", "https://www.patexplorer.com"); + headers.put("X-Requested-With", "XMLHttpRequest"); + headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + headers.put("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"); + headers.put("Accept", "*/*"); + headers.put("Accept-Encoding", "gzip, deflate, br"); + headers.put("Connection", "keep-alive"); + headers.put("Content-Length", "199"); + + Map data = new HashMap<>(); + data.put("sc", ""); + data.put("q", "武汉瑞博科创生物技术有限公司"); + data.put("sort", ""); + data.put("sortField", ""); + data.put("fq", ""); + data.put("pageSize", ""); + data.put("pageIndex", ""); + data.put("type", "s"); + data.put("merge", "no-merge"); + + + Document post = Jsoup + .connect("https://www.patexplorer.com/results/list/YzhkYWIwYzRlZWIwZmJmZDRiMGMyZDRjOTdjZmM4MmU=") + .headers(headers).data(data).post(); + System.out.println(post.body()); + } +} diff --git a/quick-vw-crawler/src/main/java/com/crawler/vw/cache/DataCache.java b/quick-vw-crawler/src/main/java/com/crawler/vw/cache/DataCache.java new file mode 100644 index 00000000..a18b01ee --- /dev/null +++ b/quick-vw-crawler/src/main/java/com/crawler/vw/cache/DataCache.java @@ -0,0 +1,30 @@ +package com.crawler.vw.cache; + +import com.alibaba.fastjson.JSON; +import com.crawler.vw.entity.Blog; +import com.crawler.vw.utils.Md5Util; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +public class DataCache { + + @Autowired + private StringRedisTemplate redisTemplate; + + public void save(Blog blog) { + redisTemplate.opsForValue().set(blog.getUrlMd5(), JSON.toJSONString(blog)); + } + + public Blog get(String url) { + String md5Url = Md5Util.getMD5(url.getBytes()); + String blogStr = redisTemplate.opsForValue().get(md5Url); + if (StringUtils.isEmpty(blogStr)) { + return new Blog(); + } + return JSON.parseObject(blogStr, Blog.class); + } + +} diff --git a/quick-vw-crawler/src/main/java/com/crawler/vw/entity/Blog.java b/quick-vw-crawler/src/main/java/com/crawler/vw/entity/Blog.java new file mode 100644 index 00000000..31d242c2 --- /dev/null +++ b/quick-vw-crawler/src/main/java/com/crawler/vw/entity/Blog.java @@ -0,0 +1,127 @@ +package com.crawler.vw.entity; + +import com.github.vector4wang.annotation.CssSelector; +import com.github.vector4wang.util.SelectType; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +public class Blog implements Serializable { + + @CssSelector(selector = "#mainBox > main > div.blog-content-box > div.article-header-box > div > div.article-info-box > div > span.time", dateFormat = "yyyy年MM月dd日 HH:mm:ss") + private Date publishDate; + + @CssSelector(selector = "main > div.blog-content-box > div.article-header-box > div.article-header>div.article-title-box > h1", resultType = SelectType.TEXT) + private String title; + + @CssSelector(selector = "main > div.blog-content-box > div.article-header-box > div.article-header>div.article-info-box > div > div > span.read-count", resultType = SelectType.TEXT) + private String readCountStr; + + private int readCount; + + @CssSelector(selector = "#article_content",resultType = SelectType.TEXT) + private String content; + + @CssSelector(selector = "body > div.tool-box > ul > li:nth-child(1) > button > p",resultType = SelectType.TEXT) + private int likeCount; + + /** + * 暂时不支持自动解析列表的功能,所以加个中间变量,需要二次解析下 + */ + @CssSelector(selector = "#mainBox > main > div.comment-box > div.comment-list-container > div.comment-list-box",resultType = SelectType.HTML) + private String comentTmp; + + private String url; + + private String urlMd5; + + private List comment; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Date getPublishDate() { + return publishDate; + } + + public String getReadCountStr() { + return readCountStr; + } + + public void setReadCountStr(String readCountStr) { + this.readCountStr = readCountStr; + } + + public void setPublishDate(Date publishDate) { + this.publishDate = publishDate; + } + + public int getReadCount() { + return readCount; + } + + public void setReadCount(int readCount) { + this.readCount = readCount; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public int getLikeCount() { + return likeCount; + } + + public void setLikeCount(int likeCount) { + this.likeCount = likeCount; + } + + public String getComentTmp() { + return comentTmp; + } + + public void setComentTmp(String comentTmp) { + this.comentTmp = comentTmp; + } + + public List getComment() { + return comment; + } + + public void setComment(List comment) { + this.comment = comment; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUrlMd5() { + return urlMd5; + } + + public void setUrlMd5(String urlMd5) { + this.urlMd5 = urlMd5; + } + + @Override + public String toString() { + return "Blog{" + "title='" + title + '\'' + ", publishDate=" + publishDate + ", readCountStr='" + readCountStr + + '\'' + ", readCount=" + readCount + ", content='" + content + '\'' + ", likeCount=" + likeCount + + ", comentTmp='" + comentTmp + '\'' + ", url='" + url + '\'' + ", comment=" + comment + '}'; + } +} diff --git a/quick-vw-crawler/src/main/java/com/crawler/vw/init/Crawler.java b/quick-vw-crawler/src/main/java/com/crawler/vw/init/Crawler.java new file mode 100644 index 00000000..833af10c --- /dev/null +++ b/quick-vw-crawler/src/main/java/com/crawler/vw/init/Crawler.java @@ -0,0 +1,47 @@ +package com.crawler.vw.init; + +import com.crawler.vw.cache.DataCache; +import com.crawler.vw.entity.Blog; +import com.crawler.vw.utils.Md5Util; +import com.github.vector4wang.VWCrawler; +import com.github.vector4wang.service.CrawlerService; +import org.jsoup.nodes.Document; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component +@Order +public class Crawler implements CommandLineRunner { + + @Autowired + private DataCache dataCache; + + @Override + public void run(String... strings) { + new VWCrawler.Builder().setUrl("https://blog.csdn.net/qqHJQS").setHeader("User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36") + .setTargetUrlRex("https://blog.csdn.net/qqHJQS/article/details/[0-9]+","https://blog.csdn.net/qqhjqs/article/details/[0-9]+").setThreadCount(5) + .setThreadCount(5) + .setTimeOut(5000).setPageParser(new CrawlerService() { + + @Override + public void parsePage(Document doc, Blog pageObj) { + pageObj.setReadCount(Integer.parseInt(pageObj.getReadCountStr().replace("阅读数:", ""))); + pageObj.setUrl(doc.baseUri()); + pageObj.setUrlMd5(Md5Util.getMD5(pageObj.getUrl().getBytes())); + + /** + * todo 评论列表还未处理 + */ + } + + @Override + public void save(Blog pageObj) { + dataCache.save(pageObj); + } + }).build().start(); + } + +} diff --git a/quick-vw-crawler/src/main/java/com/crawler/vw/utils/Md5Util.java b/quick-vw-crawler/src/main/java/com/crawler/vw/utils/Md5Util.java new file mode 100644 index 00000000..e5f78b2c --- /dev/null +++ b/quick-vw-crawler/src/main/java/com/crawler/vw/utils/Md5Util.java @@ -0,0 +1,30 @@ +package com.crawler.vw.utils; + +public class Md5Util { + public static String getMD5(byte[] source) { + String s = null; + char hexDigits[] = { // 用来将字节转换成 16 进制表示的字符 + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); + md.update(source); + byte tmp[] = md.digest(); // MD5 的计算结果是一个 128 位的长整数, + // 用字节表示就是 16 个字节 + char str[] = new char[16 * 2]; // 每个字节用 16 进制表示的话,使用两个字符, + // 所以表示成 16 进制需要 32 个字符 + int k = 0; // 表示转换结果中对应的字符位置 + for (int i = 0; i < 16; i++) { // 从第一个字节开始,对 MD5 的每一个字节 + // 转换成 16 进制字符的转换 + byte byte0 = tmp[i]; // 取第 i 个字节 + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换, + // >>> 为逻辑右移,将符号位一起右移 + str[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换 + } + s = new String(str); // 换后的结果转换为字符串 + + } catch (Exception e) { + e.printStackTrace(); + } + return s; + } +} diff --git "a/quick-vw-crawler/src/main/resources/WX20180722-103513@2x\347\232\204\345\211\257\346\234\254.png" "b/quick-vw-crawler/src/main/resources/WX20180722-103513@2x\347\232\204\345\211\257\346\234\254.png" new file mode 100644 index 00000000..c1c66655 Binary files /dev/null and "b/quick-vw-crawler/src/main/resources/WX20180722-103513@2x\347\232\204\345\211\257\346\234\254.png" differ diff --git a/quick-vw-crawler/src/main/resources/WX20180722-110714@2x.png b/quick-vw-crawler/src/main/resources/WX20180722-110714@2x.png new file mode 100644 index 00000000..53446b8b Binary files /dev/null and b/quick-vw-crawler/src/main/resources/WX20180722-110714@2x.png differ diff --git a/quick-vw-crawler/src/main/resources/application.properties b/quick-vw-crawler/src/main/resources/application.properties new file mode 100644 index 00000000..17847c23 --- /dev/null +++ b/quick-vw-crawler/src/main/resources/application.properties @@ -0,0 +1,19 @@ + +# Redis数据库索引(默认为0) +spring.redis.database=0 +# Redis服务器地址 +spring.redis.host=localhost +# Redis服务器连接端口 +spring.redis.port=6379 +# Redis服务器连接密码(默认为空) +spring.redis.password= +# 连接池最大连接数(使用负值表示没有限制) +spring.redis.pool.max-active=8 +# 连接池最大阻塞等待时间(使用负值表示没有限制) +spring.redis.pool.max-wait=-1 +# 连接池中的最大空闲连接 +spring.redis.pool.max-idle=8 +# 连接池中的最小空闲连接 +spring.redis.pool.min-idle=0 +# 连接超时时间(毫秒) +spring.redis.timeout=0 diff --git a/quick-wx-api/pom.xml b/quick-wx-api/pom.xml deleted file mode 100644 index 1775a3d3..00000000 --- a/quick-wx-api/pom.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - quick-wx-api - com.quick - 1.0 - 4.0.0 - war - - - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - - - - - 2.2.2 - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - - - org.springframework.boot - spring-boot-starter-tomcat - provided - - - org.springframework.boot - spring-boot-starter-log4j2 - - - org.springframework.boot - spring-boot-starter-aop - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.apache.httpcomponents - httpclient - 4.2.1 - - - org.apache.httpcomponents - httpcore - 4.2.1 - - - commons-lang - commons-lang - 2.6 - - - org.jsoup - jsoup - 1.7.2 - - - - com.alibaba - fastjson - 1.2.7 - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - com.quick.api.Application - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - - \ No newline at end of file diff --git a/quick-wx-api/quick-wx-api.zip b/quick-wx-api/quick-wx-api.zip deleted file mode 100644 index 83905b4d..00000000 Binary files a/quick-wx-api/quick-wx-api.zip and /dev/null differ diff --git a/quick-wx-api/src/main/java/com/quick/api/Application.java b/quick-wx-api/src/main/java/com/quick/api/Application.java deleted file mode 100644 index b14cd2aa..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/Application.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.quick.api; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.context.web.SpringBootServletInitializer; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -@SpringBootApplication -public class Application extends SpringBootServletInitializer { - - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { - return application.sources(Application.class); - } - - public static void main(String[] args) { - SpringApplication.run(Application.class,args); - } -} diff --git a/quick-wx-api/src/main/java/com/quick/api/config/WebLogAspect.java b/quick-wx-api/src/main/java/com/quick/api/config/WebLogAspect.java deleted file mode 100644 index 5aaf7143..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/config/WebLogAspect.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.quick.api.config; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.*; -import org.springframework.stereotype.Component; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import java.util.Arrays; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -@Aspect -@Component -public class WebLogAspect { - - private static final Logger loggger = LogManager.getLogger(WebLogAspect.class); - - @Pointcut("execution(public * com.quick.api..controller.*.*(..))")//两个..代表所有子目录,最后括号里的两个..代表所有参数 - public void logPointCut() { - } - - @Before("logPointCut()") - public void doBefore(JoinPoint joinPoint) throws Throwable { - // 接收到请求,记录请求内容 - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - HttpServletRequest request = attributes.getRequest(); - - // 记录下请求内容 - loggger.info("请求地址 : " + request.getRequestURL().toString()); - loggger.info("HTTP METHOD : " + request.getMethod()); - loggger.info("IP : " + request.getRemoteAddr()); - loggger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." - + joinPoint.getSignature().getName()); - loggger.info("参数 : " + Arrays.toString(joinPoint.getArgs())); -// loggger.info("参数 : " + joinPoint.getArgs()); - - } - - @AfterReturning(returning = "ret", pointcut = "logPointCut()")// returning的值和doAfterReturning的参数名一致 - public void doAfterReturning(Object ret) throws Throwable { - // 处理完请求,返回内容 - loggger.info("返回值 : " + ret); - } - - @Around("logPointCut()") - public Object doAround(ProceedingJoinPoint pjp) throws Throwable { - long startTime = System.currentTimeMillis(); - Object ob = pjp.proceed();// ob 为方法的返回值 - loggger.info("耗时 : " + (System.currentTimeMillis() - startTime)); - return ob; - } -} diff --git a/quick-wx-api/src/main/java/com/quick/api/controller/ApiController.java b/quick-wx-api/src/main/java/com/quick/api/controller/ApiController.java deleted file mode 100644 index 91b87a4c..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/controller/ApiController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.quick.api.controller; - -import com.alibaba.fastjson.JSONArray; -import com.quick.api.service.NewService; -import com.quick.api.utils.BaseResp; -import com.quick.api.utils.ResultStatus; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.io.IOException; -import java.util.List; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -@RestController -@RequestMapping("/api") -public class ApiController { - - @Resource - private NewService newService; - - @RequestMapping("/news") - public BaseResp news(){ - JSONArray news = newService.getNews(); - return new BaseResp<>(ResultStatus.SUCCESS,news); - } - - @RequestMapping("/content") - public BaseResp getContent(@RequestParam("url")String url){ - String content = "404"; - try { - content = newService.getContent(url); - } catch (IOException e) { - e.printStackTrace(); - } - return new BaseResp<>(ResultStatus.SUCCESS,content); - } -} diff --git a/quick-wx-api/src/main/java/com/quick/api/service/NewService.java b/quick-wx-api/src/main/java/com/quick/api/service/NewService.java deleted file mode 100644 index c579d491..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/service/NewService.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.quick.api.service; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.quick.api.utils.HttpUtils; -import org.apache.http.HttpResponse; -import org.apache.http.util.EntityUtils; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.select.Elements; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -@Service -public class NewService { - - private final static String host = "http://toutiao-ali.juheapi.com"; - private final static String path = "/toutiao/index"; - private final static String method = "GET"; - private final static String appcode = "8852ff1c52e84e7595d1425bc99813c2"; - -// public static void main(String[] args) { -// try { -// String content = getContent("http://mini.eastday.com/mobile/170711205356140.html"); -// }catch (Exception e){ -// e.printStackTrace(); -// } -// -// } - - - - public static JSONArray getNews() { - JSONArray jsonArray = new JSONArray(); - Map headers = new HashMap(); - //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105 - headers.put("Authorization", "APPCODE " + appcode); - Map querys = new HashMap<>(); - querys.put("type", "type"); - - - try { - HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys); -// System.out.println(response.toString()); - //获取response的body - String result = EntityUtils.toString(response.getEntity()); - JSONObject jsonObject = JSON.parseObject(result); - jsonArray = jsonObject.getJSONObject("result").getJSONArray("data"); - } catch (Exception e) { - e.printStackTrace(); - } - return jsonArray; - } - - public static String getContent(String url) throws IOException { - Document parse = Jsoup.parse(new URL(url), 5000); - Elements select = parse.select("div#content"); - String content = select.get(0).html(); - return content; - } - - public static void main(String[] args) throws IOException { - Document parse = Jsoup.parse(new URL("http://mini.eastday.com/mobile/170725205647104.html"), 5000); - Elements select = parse.select("div#content"); - String content = select.get(0).html(); - - System.out.println(content); - } -} diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/Constants.java b/quick-wx-api/src/main/java/com/quick/api/utils/Constants.java deleted file mode 100644 index 3d3c6c35..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/Constants.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.quick.api.utils; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -/** - * 通用常量 - */ -public class Constants { - //签名算法HmacSha256 - public static final String HMAC_SHA256 = "HmacSHA256"; - //编码UTF-8 - public static final String ENCODING = "UTF-8"; - //UserAgent - public static final String USER_AGENT = "demo/aliyun/java"; - //换行符 - public static final String LF = "\n"; - //串联符 - public static final String SPE1 = ","; - //示意符 - public static final String SPE2 = ":"; - //连接符 - public static final String SPE3 = "&"; - //赋值符 - public static final String SPE4 = "="; - //问号符 - public static final String SPE5 = "?"; - //默认请求超时时间,单位毫秒 - public static final int DEFAULT_TIMEOUT = 1000; - //参与签名的系统Header前缀,只有指定前缀的Header才会参与到签名中 - public static final String CA_HEADER_TO_SIGN_PREFIX_SYSTEM = "X-Ca-"; -} \ No newline at end of file diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/ContentType.java b/quick-wx-api/src/main/java/com/quick/api/utils/ContentType.java deleted file mode 100644 index 4996a096..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/ContentType.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.quick.api.utils; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -public class ContentType { - //表单类型Content-Type - public static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded; charset=UTF-8"; - // 流类型Content-Type - public static final String CONTENT_TYPE_STREAM = "application/octet-stream; charset=UTF-8"; - //JSON类型Content-Type - public static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8"; - //XML类型Content-Type - public static final String CONTENT_TYPE_XML = "application/xml; charset=UTF-8"; - //文本类型Content-Type - public static final String CONTENT_TYPE_TEXT = "application/text; charset=UTF-8"; -} \ No newline at end of file diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/HttpHeader.java b/quick-wx-api/src/main/java/com/quick/api/utils/HttpHeader.java deleted file mode 100644 index e902a815..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/HttpHeader.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.quick.api.utils; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -public class HttpHeader { - //请求Header Accept - public static final String HTTP_HEADER_ACCEPT = "Accept"; - //请求Body内容MD5 Header - public static final String HTTP_HEADER_CONTENT_MD5 = "Content-MD5"; - //请求Header Content-Type - public static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type"; - //请求Header UserAgent - public static final String HTTP_HEADER_USER_AGENT = "User-Agent"; - //请求Header Date - public static final String HTTP_HEADER_DATE = "Date"; -} \ No newline at end of file diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/HttpMethod.java b/quick-wx-api/src/main/java/com/quick/api/utils/HttpMethod.java deleted file mode 100644 index acd51cfd..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/HttpMethod.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.quick.api.utils; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -public class HttpMethod { - //GET - public static final String GET = "GET"; - //POST - public static final String POST = "POST"; - //PUT - public static final String PUT = "PUT"; - //DELETE - public static final String DELETE = "DELETE"; -} \ No newline at end of file diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/HttpUtil.java b/quick-wx-api/src/main/java/com/quick/api/utils/HttpUtil.java deleted file mode 100644 index 9bfda65b..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/HttpUtil.java +++ /dev/null @@ -1,471 +0,0 @@ -package com.quick.api.utils; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URLEncoder; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.commons.lang.StringUtils; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.params.CoreConnectionPNames; - - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -public class HttpUtil { - /** - * HTTP GET - * @param host - * @param path - * @param connectTimeout - * @param headers - * @param querys - * @param signHeaderPrefixList - * @param appKey - * @param appSecret - * @return - * @throws Exception - */ - public static Response httpGet(String host, String path, int connectTimeout, Map headers, Map querys, List signHeaderPrefixList, String appKey, String appSecret) - throws Exception { - headers = initialBasicHeader(HttpMethod.GET, path, headers, querys, null, signHeaderPrefixList, appKey, appSecret); - - HttpClient httpClient = wrapClient(host); - httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, getTimeout(connectTimeout)); - - HttpGet get = new HttpGet(initUrl(host, path, querys)); - - for (Map.Entry e : headers.entrySet()) { - get.addHeader(e.getKey(), MessageDigestUtil.utf8ToIso88591(e.getValue())); - } - - return convert(httpClient.execute(get)); - } - - /** - * HTTP POST表单 - * @param host - * @param path - * @param connectTimeout - * @param headers - * @param querys - * @param bodys - * @param signHeaderPrefixList - * @param appKey - * @param appSecret - * @return - * @throws Exception - */ - public static Response httpPost(String host, String path, int connectTimeout, Map headers, Map querys, Map bodys, List signHeaderPrefixList, String appKey, String appSecret) - throws Exception { - if (headers == null) { - headers = new HashMap(); - } - - headers.put(HttpHeader.HTTP_HEADER_CONTENT_TYPE, ContentType.CONTENT_TYPE_FORM); - - headers = initialBasicHeader(HttpMethod.POST, path, headers, querys, bodys, signHeaderPrefixList, appKey, appSecret); - - HttpClient httpClient = wrapClient(host); - httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, getTimeout(connectTimeout)); - - HttpPost post = new HttpPost(initUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - post.addHeader(e.getKey(), MessageDigestUtil.utf8ToIso88591(e.getValue())); - } - - UrlEncodedFormEntity formEntity = buildFormEntity(bodys); - if (formEntity != null) { - post.setEntity(formEntity); - } - - return convert(httpClient.execute(post)); - } - - /** - * Http POST 字符串 - * @param host - * @param path - * @param connectTimeout - * @param headers - * @param querys - * @param body - * @param signHeaderPrefixList - * @param appKey - * @param appSecret - * @return - * @throws Exception - */ - public static Response httpPost(String host, String path, int connectTimeout, Map headers, Map querys, String body, List signHeaderPrefixList, String appKey, String appSecret) - throws Exception { - headers = initialBasicHeader(HttpMethod.POST, path, headers, querys, null, signHeaderPrefixList, appKey, appSecret); - - HttpClient httpClient = wrapClient(host); - httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, getTimeout(connectTimeout)); - - HttpPost post = new HttpPost(initUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - post.addHeader(e.getKey(), MessageDigestUtil.utf8ToIso88591(e.getValue())); - } - - if (StringUtils.isNotBlank(body)) { - post.setEntity(new StringEntity(body, Constants.ENCODING)); - - } - - return convert(httpClient.execute(post)); - } - - /** - * HTTP POST 字节数组 - * @param host - * @param path - * @param connectTimeout - * @param headers - * @param querys - * @param bodys - * @param signHeaderPrefixList - * @param appKey - * @param appSecret - * @return - * @throws Exception - */ - public static Response httpPost(String host, String path, int connectTimeout, Map headers, Map querys, byte[] bodys, List signHeaderPrefixList, String appKey, String appSecret) - throws Exception { - headers = initialBasicHeader(HttpMethod.POST, path, headers, querys, null, signHeaderPrefixList, appKey, appSecret); - - HttpClient httpClient = wrapClient(host); - httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, getTimeout(connectTimeout)); - - HttpPost post = new HttpPost(initUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - post.addHeader(e.getKey(), MessageDigestUtil.utf8ToIso88591(e.getValue())); - } - - if (bodys != null) { - post.setEntity(new ByteArrayEntity(bodys)); - } - - return convert(httpClient.execute(post)); - } - - /** - * HTTP PUT 字符串 - * @param host - * @param path - * @param connectTimeout - * @param headers - * @param querys - * @param body - * @param signHeaderPrefixList - * @param appKey - * @param appSecret - * @return - * @throws Exception - */ - public static Response httpPut(String host, String path, int connectTimeout, Map headers, Map querys, String body, List signHeaderPrefixList, String appKey, String appSecret) - throws Exception { - headers = initialBasicHeader(HttpMethod.PUT, path, headers, querys, null, signHeaderPrefixList, appKey, appSecret); - - HttpClient httpClient = wrapClient(host); - httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, getTimeout(connectTimeout)); - - HttpPut put = new HttpPut(initUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - put.addHeader(e.getKey(), MessageDigestUtil.utf8ToIso88591(e.getValue())); - } - - if (StringUtils.isNotBlank(body)) { - put.setEntity(new StringEntity(body, Constants.ENCODING)); - - } - - return convert(httpClient.execute(put)); - } - - /** - * HTTP PUT字节数组 - * @param host - * @param path - * @param connectTimeout - * @param headers - * @param querys - * @param bodys - * @param signHeaderPrefixList - * @param appKey - * @param appSecret - * @return - * @throws Exception - */ - public static Response httpPut(String host, String path, int connectTimeout, Map headers, Map querys, byte[] bodys, List signHeaderPrefixList, String appKey, String appSecret) - throws Exception { - headers = initialBasicHeader(HttpMethod.PUT, path, headers, querys, null, signHeaderPrefixList, appKey, appSecret); - - HttpClient httpClient = wrapClient(host); - httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, getTimeout(connectTimeout)); - - HttpPut put = new HttpPut(initUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - put.addHeader(e.getKey(), MessageDigestUtil.utf8ToIso88591(e.getValue())); - } - - if (bodys != null) { - put.setEntity(new ByteArrayEntity(bodys)); - } - - return convert(httpClient.execute(put)); - } - - /** - * HTTP DELETE - * @param host - * @param path - * @param connectTimeout - * @param headers - * @param querys - * @param signHeaderPrefixList - * @param appKey - * @param appSecret - * @return - * @throws Exception - */ - public static Response httpDelete(String host, String path, int connectTimeout, Map headers, Map querys, List signHeaderPrefixList, String appKey, String appSecret) - throws Exception { - headers = initialBasicHeader(HttpMethod.DELETE, path, headers, querys, null, signHeaderPrefixList, appKey, appSecret); - - HttpClient httpClient = wrapClient(host); - httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, getTimeout(connectTimeout)); - - HttpDelete delete = new HttpDelete(initUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - delete.addHeader(e.getKey(), MessageDigestUtil.utf8ToIso88591(e.getValue())); - } - - return convert(httpClient.execute(delete)); - } - - /** - * 构建FormEntity - * - * @param formParam - * @return - * @throws UnsupportedEncodingException - */ - private static UrlEncodedFormEntity buildFormEntity(Map formParam) - throws UnsupportedEncodingException { - if (formParam != null) { - List nameValuePairList = new ArrayList(); - - for (String key : formParam.keySet()) { - nameValuePairList.add(new BasicNameValuePair(key, formParam.get(key))); - } - UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, Constants.ENCODING); - formEntity.setContentType(ContentType.CONTENT_TYPE_FORM); - return formEntity; - } - - return null; - } - - private static String initUrl(String host, String path, Map querys) throws UnsupportedEncodingException { - StringBuilder sbUrl = new StringBuilder(); - sbUrl.append(host); - if (!StringUtils.isBlank(path)) { - sbUrl.append(path); - } - if (null != querys) { - StringBuilder sbQuery = new StringBuilder(); - for (Map.Entry query : querys.entrySet()) { - if (0 < sbQuery.length()) { - sbQuery.append(Constants.SPE3); - } - if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) { - sbQuery.append(query.getValue()); - } - if (!StringUtils.isBlank(query.getKey())) { - sbQuery.append(query.getKey()); - if (!StringUtils.isBlank(query.getValue())) { - sbQuery.append(Constants.SPE4); - sbQuery.append(URLEncoder.encode(query.getValue(), Constants.ENCODING)); - } - } - } - if (0 < sbQuery.length()) { - sbUrl.append(Constants.SPE5).append(sbQuery); - } - } - - return sbUrl.toString(); - } - - - /** - * 初始化基础Header - * @param method - * @param path - * @param headers - * @param querys - * @param bodys - * @param signHeaderPrefixList - * @param appKey - * @param appSecret - * @return - * @throws MalformedURLException - */ - private static Map initialBasicHeader(String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList, - String appKey, String appSecret) - throws MalformedURLException { - if (headers == null) { - headers = new HashMap(); - } - - headers.put(SystemHeader.X_CA_TIMESTAMP, String.valueOf(new Date().getTime())); - //headers.put(SystemHeader.X_CA_NONCE, UUID.randomUUID().toString()); - headers.put(SystemHeader.X_CA_KEY, appKey); - headers.put(SystemHeader.X_CA_SIGNATURE, - SignUtil.sign(appSecret, method, path, headers, querys, bodys, signHeaderPrefixList)); - - return headers; - } - - /** - * 读取超时时间 - * - * @param timeout - * @return - */ - private static int getTimeout(int timeout) { - if (timeout == 0) { - return Constants.DEFAULT_TIMEOUT; - } - - return timeout; - } - - private static Response convert(HttpResponse response) throws IOException { - Response res = new Response(); - - if (null != response) { - res.setStatusCode(response.getStatusLine().getStatusCode()); - for (Header header : response.getAllHeaders()) { - res.setHeader(header.getName(), MessageDigestUtil.iso88591ToUtf8(header.getValue())); - } - - res.setContentType(res.getHeader("Content-Type")); - res.setRequestId(res.getHeader("X-Ca-Request-Id")); - res.setErrorMessage(res.getHeader("X-Ca-Error-Message")); - res.setBody(readStreamAsStr(response.getEntity().getContent())); - - } else { - //服务器无回应 - res.setStatusCode(500); - res.setErrorMessage("No Response"); - } - - return res; - } - - - /** - * 将流转换为字符串 - * - * @param is - * @return - * @throws IOException - */ - public static String readStreamAsStr(InputStream is) throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - WritableByteChannel dest = Channels.newChannel(bos); - ReadableByteChannel src = Channels.newChannel(is); - ByteBuffer bb = ByteBuffer.allocate(4096); - - while (src.read(bb) != -1) { - bb.flip(); - dest.write(bb); - bb.clear(); - } - src.close(); - dest.close(); - - return new String(bos.toByteArray(), Constants.ENCODING); - } - - private static HttpClient wrapClient(String host) { - HttpClient httpClient = new DefaultHttpClient(); - if (host.startsWith("https://")) { - sslClient(httpClient); - } - - return httpClient; - } - - private static void sslClient(HttpClient httpClient) { - try { - SSLContext ctx = SSLContext.getInstance("TLS"); - X509TrustManager tm = new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return null; - } - public void checkClientTrusted(X509Certificate[] xcs, String str) { - - } - public void checkServerTrusted(X509Certificate[] xcs, String str) { - - } - }; - ctx.init(null, new TrustManager[] { tm }, null); - SSLSocketFactory ssf = new SSLSocketFactory(ctx); - ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - ClientConnectionManager ccm = httpClient.getConnectionManager(); - SchemeRegistry registry = ccm.getSchemeRegistry(); - registry.register(new Scheme("https", 443, ssf)); - } catch (KeyManagementException ex) { - throw new RuntimeException(ex); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex); - } - } -} \ No newline at end of file diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/HttpUtils.java b/quick-wx-api/src/main/java/com/quick/api/utils/HttpUtils.java deleted file mode 100644 index 2a28878e..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/HttpUtils.java +++ /dev/null @@ -1,319 +0,0 @@ -package com.quick.api.utils; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.commons.lang.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -public class HttpUtils { - - /** - * get - * - * @param host - * @param path - * @param method - * @param headers - * @param querys - * @return - * @throws Exception - */ - public static HttpResponse doGet(String host, String path, String method, - Map headers, - Map querys) - throws Exception { - HttpClient httpClient = wrapClient(host); - - HttpGet request = new HttpGet(buildUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - request.addHeader(e.getKey(), e.getValue()); - } - - return httpClient.execute(request); - } - - /** - * post form - * - * @param host - * @param path - * @param method - * @param headers - * @param querys - * @param bodys - * @return - * @throws Exception - */ - public static HttpResponse doPost(String host, String path, String method, - Map headers, - Map querys, - Map bodys) - throws Exception { - HttpClient httpClient = wrapClient(host); - - HttpPost request = new HttpPost(buildUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - request.addHeader(e.getKey(), e.getValue()); - } - - if (bodys != null) { - List nameValuePairList = new ArrayList(); - - for (String key : bodys.keySet()) { - nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key))); - } - UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8"); - formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8"); - request.setEntity(formEntity); - } - - return httpClient.execute(request); - } - - /** - * Post String - * - * @param host - * @param path - * @param method - * @param headers - * @param querys - * @param body - * @return - * @throws Exception - */ - public static HttpResponse doPost(String host, String path, String method, - Map headers, - Map querys, - String body) - throws Exception { - HttpClient httpClient = wrapClient(host); - - HttpPost request = new HttpPost(buildUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - request.addHeader(e.getKey(), e.getValue()); - } - - if (StringUtils.isNotBlank(body)) { - request.setEntity(new StringEntity(body, "utf-8")); - } - - return httpClient.execute(request); - } - - /** - * Post stream - * - * @param host - * @param path - * @param method - * @param headers - * @param querys - * @param body - * @return - * @throws Exception - */ - public static HttpResponse doPost(String host, String path, String method, - Map headers, - Map querys, - byte[] body) - throws Exception { - HttpClient httpClient = wrapClient(host); - - HttpPost request = new HttpPost(buildUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - request.addHeader(e.getKey(), e.getValue()); - } - - if (body != null) { - request.setEntity(new ByteArrayEntity(body)); - } - - return httpClient.execute(request); - } - - /** - * Put String - * @param host - * @param path - * @param method - * @param headers - * @param querys - * @param body - * @return - * @throws Exception - */ - public static HttpResponse doPut(String host, String path, String method, - Map headers, - Map querys, - String body) - throws Exception { - HttpClient httpClient = wrapClient(host); - - HttpPut request = new HttpPut(buildUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - request.addHeader(e.getKey(), e.getValue()); - } - - if (StringUtils.isNotBlank(body)) { - request.setEntity(new StringEntity(body, "utf-8")); - } - - return httpClient.execute(request); - } - - /** - * Put stream - * @param host - * @param path - * @param method - * @param headers - * @param querys - * @param body - * @return - * @throws Exception - */ - public static HttpResponse doPut(String host, String path, String method, - Map headers, - Map querys, - byte[] body) - throws Exception { - HttpClient httpClient = wrapClient(host); - - HttpPut request = new HttpPut(buildUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - request.addHeader(e.getKey(), e.getValue()); - } - - if (body != null) { - request.setEntity(new ByteArrayEntity(body)); - } - - return httpClient.execute(request); - } - - /** - * Delete - * - * @param host - * @param path - * @param method - * @param headers - * @param querys - * @return - * @throws Exception - */ - public static HttpResponse doDelete(String host, String path, String method, - Map headers, - Map querys) - throws Exception { - HttpClient httpClient = wrapClient(host); - - HttpDelete request = new HttpDelete(buildUrl(host, path, querys)); - for (Map.Entry e : headers.entrySet()) { - request.addHeader(e.getKey(), e.getValue()); - } - - return httpClient.execute(request); - } - - private static String buildUrl(String host, String path, Map querys) throws UnsupportedEncodingException { - StringBuilder sbUrl = new StringBuilder(); - sbUrl.append(host); - if (!StringUtils.isBlank(path)) { - sbUrl.append(path); - } - if (null != querys) { - StringBuilder sbQuery = new StringBuilder(); - for (Map.Entry query : querys.entrySet()) { - if (0 < sbQuery.length()) { - sbQuery.append("&"); - } - if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) { - sbQuery.append(query.getValue()); - } - if (!StringUtils.isBlank(query.getKey())) { - sbQuery.append(query.getKey()); - if (!StringUtils.isBlank(query.getValue())) { - sbQuery.append("="); - sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8")); - } - } - } - if (0 < sbQuery.length()) { - sbUrl.append("?").append(sbQuery); - } - } - - return sbUrl.toString(); - } - - private static HttpClient wrapClient(String host) { - HttpClient httpClient = new DefaultHttpClient(); - if (host.startsWith("https://")) { - sslClient(httpClient); - } - - return httpClient; - } - - private static void sslClient(HttpClient httpClient) { - try { - SSLContext ctx = SSLContext.getInstance("TLS"); - X509TrustManager tm = new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return null; - } - public void checkClientTrusted(X509Certificate[] xcs, String str) { - - } - public void checkServerTrusted(X509Certificate[] xcs, String str) { - - } - }; - ctx.init(null, new TrustManager[] { tm }, null); - SSLSocketFactory ssf = new SSLSocketFactory(ctx); - ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - ClientConnectionManager ccm = httpClient.getConnectionManager(); - SchemeRegistry registry = ccm.getSchemeRegistry(); - registry.register(new Scheme("https", 443, ssf)); - } catch (KeyManagementException ex) { - throw new RuntimeException(ex); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex); - } - } -} \ No newline at end of file diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/MessageDigestUtil.java b/quick-wx-api/src/main/java/com/quick/api/utils/MessageDigestUtil.java deleted file mode 100644 index 53248162..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/MessageDigestUtil.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.quick.api.utils; - -import org.apache.commons.codec.binary.Base64; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ - -/** - * 消息摘要工具 - */ -public class MessageDigestUtil { - /** - * 先进行MD5摘要再进行Base64编码获取摘要字符串 - * - * @param str - * @return - */ - public static String base64AndMD5(String str) { - if (str == null) { - throw new IllegalArgumentException("inStr can not be null"); - } - return base64AndMD5(toBytes(str)); - } - - /** - * 先进行MD5摘要再进行Base64编码获取摘要字符串 - * - * @return - */ - public static String base64AndMD5(byte[] bytes) { - if (bytes == null) { - throw new IllegalArgumentException("bytes can not be null"); - } - try { - final MessageDigest md = MessageDigest.getInstance("MD5"); - md.reset(); - md.update(bytes); - final Base64 base64 = new Base64(); - final byte[] enbytes = base64.encode(md.digest()); - return new String(enbytes); - } catch (final NoSuchAlgorithmException e) { - throw new IllegalArgumentException("unknown algorithm MD5"); - } - } - - /** - * UTF-8编码转换为ISO-9959-1 - * - * @param str - * @return - */ - public static String utf8ToIso88591(String str) { - if (str == null) { - return str; - } - - try { - return new String(str.getBytes("UTF-8"), "ISO-8859-1"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - - /** - * ISO-9959-1编码转换为UTF-8 - * - * @param str - * @return - */ - public static String iso88591ToUtf8(String str) { - if (str == null) { - return str; - } - - try { - return new String(str.getBytes("ISO-8859-1"), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - - /** - * String转换为字节数组 - * - * @param str - * @return - */ - private static byte[] toBytes(final String str) { - if (str == null) { - return null; - } - try { - return str.getBytes(Constants.ENCODING); - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e.getMessage(), e); - } - } -} \ No newline at end of file diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/Response.java b/quick-wx-api/src/main/java/com/quick/api/utils/Response.java deleted file mode 100644 index c0f13f79..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/Response.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.quick.api.utils; - -import java.util.HashMap; -import java.util.Map; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -public class Response { - private int statusCode; - private String contentType; - private String requestId; - private String errorMessage; - private Map headers; - private String body; - - public Response() { - - } - - public int getStatusCode() { - return statusCode; - } - - public void setStatusCode(int statusCode) { - this.statusCode = statusCode; - } - - public String getContentType() { - return contentType; - } - - public void setContentType(String contentType) { - this.contentType = contentType; - } - - public String getRequestId() { - return requestId; - } - - public void setRequestId(String requestId) { - this.requestId = requestId; - } - - public String getErrorMessage() { - return errorMessage; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } - - public Map getHeaders() { - return headers; - } - - public String getHeader(String key) { - if (null != headers) { - return headers.get(key); - } else { - return null; - } - } - - public void setHeaders(Map headers) { - this.headers = headers; - } - - public void setHeader(String key, String value) { - if (null == this.headers) { - this.headers = new HashMap(); - } - this.headers.put(key, value); - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - - -} \ No newline at end of file diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/ResultStatus.java b/quick-wx-api/src/main/java/com/quick/api/utils/ResultStatus.java deleted file mode 100644 index e1db5b46..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/ResultStatus.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.quick.api.utils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - 错误码 - * @author vector - * - */ -public enum ResultStatus { - - // -1为通用失败(根据ApiResult.java中的构造方法注释而来) - FAIL(-1, "common fail"), - // 0为成功 - SUCCESS(0, "success"), - - error_pic_file(3,"非法图片文件"), - error_pic_upload(4,"图片上传失败"), - error_record_not_found(5, "没有找到对应的数据"), - error_max_page_size(6, "请求记录数超出每次请求最大允许值"), - error_create_failed(7,"新增失败"), - error_update_failed(8,"修改失败"), - error_delete_failed(9,"删除失败"), - error_search_failed(10,"查询失败"), - error_count_failed(11,"查询数据总数失败"), - error_string_to_obj(12,"字符串转java对象失败"), - error_invalid_argument(13,"参数不合法"), - error_update_not_allowed(14,"更新失败:%s"), - error_duplicated_data(15,"数据已存在"), - error_unknown_database_operation(16,"未知数据库操作失败,请联系管理员解决"), - error_column_unique(17,"字段s%违反唯一约束性条件"), - error_file_download(18,"文件下载失败"), - error_file_upload(19,"文件上传失败"), - - //100-511为http 状态码 - // --- 4xx Client Error --- - http_status_bad_request(400, "Bad Request"), - http_status_unauthorized(401, "Unauthorized"), - http_status_payment_required(402, "Payment Required"), - http_status_forbidden(403, "Forbidden"), - http_status_not_found(404, "Not Found"), - http_status_method_not_allowed(405, "Method Not Allowed"), - http_status_not_acceptable(406, "Not Acceptable"), - http_status_proxy_authentication_required(407, "Proxy Authentication Required"), - http_status_request_timeout(408, "Request Timeout"), - http_status_conflict(409, "Conflict"), - http_status_gone(410, "Gone"), - http_status_length_required(411, "Length Required"), - http_status_precondition_failed(412, "Precondition Failed"), - http_status_payload_too_large(413, "Payload Too Large"), - http_status_uri_too_long(414, "URI Too Long"), - http_status_unsupported_media_type(415, "Unsupported Media Type"), - http_status_requested_range_not_satisfiable(416, "Requested range not satisfiable"), - http_status_expectation_failed(417, "Expectation Failed"), - http_status_im_a_teapot(418, "I'm a teapot"), - http_status_unprocessable_entity(422, "Unprocessable Entity"), - http_status_locked(423, "Locked"), - http_status_failed_dependency(424, "Failed Dependency"), - http_status_upgrade_required(426, "Upgrade Required"), - http_status_precondition_required(428, "Precondition Required"), - http_status_too_many_requests(429, "Too Many Requests"), - http_status_request_header_fields_too_large(431, "Request Header Fields Too Large"), - - // --- 5xx Server Error --- - http_status_internal_server_error(500, "系统错误"), - http_status_not_implemented(501, "Not Implemented"), - http_status_bad_gateway(502, "Bad Gateway"), - http_status_service_unavailable(503, "Service Unavailable"), - http_status_gateway_timeout(504, "Gateway Timeout"), - http_status_http_version_not_supported(505, "HTTP Version not supported"), - http_status_variant_also_negotiates(506, "Variant Also Negotiates"), - http_status_insufficient_storage(507, "Insufficient Storage"), - http_status_loop_detected(508, "Loop Detected"), - http_status_bandwidth_limit_exceeded(509, "Bandwidth Limit Exceeded"), - http_status_not_extended(510, "Not Extended"), - http_status_network_authentication_required(511, "Network Authentication Required"), - - // --- 8xx common error --- - EXCEPTION(800, "exception"), - INVALID_PARAM(801, "invalid.param"), - INVALID_PRIVI(802, "invalid.privi"), - - //1000以内是系统错误, - no_login(1000,"没有登录"), - config_error(1001,"参数配置表错误"), - user_exist(1002,"用户名已存在"), - userpwd_not_exist(1003,"用户名不存在或者密码错误"), - - - - - ; - private static final Logger LOGGER = LoggerFactory.getLogger(ResultStatus.class); - - - private int code; - private String msg; - - ResultStatus(int code, String msg){ - this.code = code; - this.msg = msg; - } - - public static int getCode(String define){ - try { - return ResultStatus.valueOf(define).code; - } catch (IllegalArgumentException e) { - LOGGER.error("undefined error code: {}", define); - return FAIL.getErrorCode(); - } - } - - public static String getMsg(String define){ - try { - return ResultStatus.valueOf(define).msg; - } catch (IllegalArgumentException e) { - LOGGER.error("undefined error code: {}", define); - return FAIL.getErrorMsg(); - } - - } - - public static String getMsg(int code){ - for(ResultStatus err : ResultStatus.values()){ - if(err.code==code){ - return err.msg; - } - } - return "errorCode not defined "; - } - - public int getErrorCode(){ - return code; - } - - public String getErrorMsg(){ - return msg; - } - -} - diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/SignUtil.java b/quick-wx-api/src/main/java/com/quick/api/utils/SignUtil.java deleted file mode 100644 index c6ba4df1..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/SignUtil.java +++ /dev/null @@ -1,215 +0,0 @@ -package com.quick.api.utils; - - - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang.StringUtils; - - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - * 签名工具 - */ -public class SignUtil { - - /** - * 计算签名 - * - * @param secret APP密钥 - * @param method HttpMethod - * @param path - * @param headers - * @param querys - * @param bodys - * @param signHeaderPrefixList 自定义参与签名Header前缀 - * @return 签名后的字符串 - */ - public static String sign(String secret, String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { - try { - Mac hmacSha256 = Mac.getInstance(Constants.HMAC_SHA256); - byte[] keyBytes = secret.getBytes(Constants.ENCODING); - hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, Constants.HMAC_SHA256)); - - return new String(Base64.encodeBase64( - hmacSha256.doFinal(buildStringToSign(method, path, headers, querys, bodys, signHeaderPrefixList) - .getBytes(Constants.ENCODING))), - Constants.ENCODING); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * 构建待签名字符串 - * @param method - * @param path - * @param headers - * @param querys - * @param bodys - * @param signHeaderPrefixList - * @return - */ - private static String buildStringToSign(String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { - StringBuilder sb = new StringBuilder(); - - sb.append(method.toUpperCase()).append(Constants.LF); - if (null != headers) { - if (null != headers.get(HttpHeader.HTTP_HEADER_ACCEPT)) { - sb.append(headers.get(HttpHeader.HTTP_HEADER_ACCEPT)); - } - sb.append(Constants.LF); - if (null != headers.get(HttpHeader.HTTP_HEADER_CONTENT_MD5)) { - sb.append(headers.get(HttpHeader.HTTP_HEADER_CONTENT_MD5)); - } - sb.append(Constants.LF); - if (null != headers.get(HttpHeader.HTTP_HEADER_CONTENT_TYPE)) { - sb.append(headers.get(HttpHeader.HTTP_HEADER_CONTENT_TYPE)); - } - sb.append(Constants.LF); - if (null != headers.get(HttpHeader.HTTP_HEADER_DATE)) { - sb.append(headers.get(HttpHeader.HTTP_HEADER_DATE)); - } - } - sb.append(Constants.LF); - sb.append(buildHeaders(headers, signHeaderPrefixList)); - sb.append(buildResource(path, querys, bodys)); - - return sb.toString(); - } - - /** - * 构建待签名Path+Query+BODY - * - * @param path - * @param querys - * @param bodys - * @return 待签名 - */ - private static String buildResource(String path, Map querys, Map bodys) { - StringBuilder sb = new StringBuilder(); - - if (!StringUtils.isBlank(path)) { - sb.append(path); - } - Map sortMap = new TreeMap(); - if (null != querys) { - for (Map.Entry query : querys.entrySet()) { - if (!StringUtils.isBlank(query.getKey())) { - sortMap.put(query.getKey(), query.getValue()); - } - } - } - - if (null != bodys) { - for (Map.Entry body : bodys.entrySet()) { - if (!StringUtils.isBlank(body.getKey())) { - sortMap.put(body.getKey(), body.getValue()); - } - } - } - - StringBuilder sbParam = new StringBuilder(); - for (Map.Entry item : sortMap.entrySet()) { - if (!StringUtils.isBlank(item.getKey())) { - if (0 < sbParam.length()) { - sbParam.append(Constants.SPE3); - } - sbParam.append(item.getKey()); - if (!StringUtils.isBlank(item.getValue())) { - sbParam.append(Constants.SPE4).append(item.getValue()); - } - } - } - if (0 < sbParam.length()) { - sb.append(Constants.SPE5); - sb.append(sbParam); - } - - return sb.toString(); - } - - /** - * 构建待签名Http头 - * - * @param headers 请求中所有的Http头 - * @param signHeaderPrefixList 自定义参与签名Header前缀 - * @return 待签名Http头 - */ - private static String buildHeaders(Map headers, List signHeaderPrefixList) { - StringBuilder sb = new StringBuilder(); - - if (null != signHeaderPrefixList) { - signHeaderPrefixList.remove(SystemHeader.X_CA_SIGNATURE); - signHeaderPrefixList.remove(HttpHeader.HTTP_HEADER_ACCEPT); - signHeaderPrefixList.remove(HttpHeader.HTTP_HEADER_CONTENT_MD5); - signHeaderPrefixList.remove(HttpHeader.HTTP_HEADER_CONTENT_TYPE); - signHeaderPrefixList.remove(HttpHeader.HTTP_HEADER_DATE); - Collections.sort(signHeaderPrefixList); - if (null != headers) { - Map sortMap = new TreeMap(); - sortMap.putAll(headers); - StringBuilder signHeadersStringBuilder = new StringBuilder(); - for (Map.Entry header : sortMap.entrySet()) { - if (isHeaderToSign(header.getKey(), signHeaderPrefixList)) { - sb.append(header.getKey()); - sb.append(Constants.SPE2); - if (!StringUtils.isBlank(header.getValue())) { - sb.append(header.getValue()); - } - sb.append(Constants.LF); - if (0 < signHeadersStringBuilder.length()) { - signHeadersStringBuilder.append(Constants.SPE1); - } - signHeadersStringBuilder.append(header.getKey()); - } - } - headers.put(SystemHeader.X_CA_SIGNATURE_HEADERS, signHeadersStringBuilder.toString()); - } - } - - return sb.toString(); - } - - /** - * Http头是否参与签名 return - */ - private static boolean isHeaderToSign(String headerName, List signHeaderPrefixList) { - if (StringUtils.isBlank(headerName)) { - return false; - } - - if (headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM)) { - return true; - } - - if (null != signHeaderPrefixList) { - for (String signHeaderPrefix : signHeaderPrefixList) { - if (headerName.equalsIgnoreCase(signHeaderPrefix)) { - return true; - } - } - } - - return false; - } -} \ No newline at end of file diff --git a/quick-wx-api/src/main/java/com/quick/api/utils/SystemHeader.java b/quick-wx-api/src/main/java/com/quick/api/utils/SystemHeader.java deleted file mode 100644 index 0050a23f..00000000 --- a/quick-wx-api/src/main/java/com/quick/api/utils/SystemHeader.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.quick.api.utils; - -/** - * @Author: wangxc - * @GitHub: https://github.com/vector4wang - * @CSDN: http://blog.csdn.net/qqhjqs?viewmode=contents - * @BLOG: http://vector4wang.tk - * @wxid: BMHJQS - */ -public class SystemHeader { - //签名Header - public static final String X_CA_SIGNATURE = "X-Ca-Signature"; - //所有参与签名的Header - public static final String X_CA_SIGNATURE_HEADERS = "X-Ca-Signature-Headers"; - //请求时间戳 - public static final String X_CA_TIMESTAMP = "X-Ca-Timestamp"; - //请求放重放Nonce,15分钟内保持唯一,建议使用UUID - public static final String X_CA_NONCE = "X-Ca-Nonce"; - //APP KEY - public static final String X_CA_KEY = "X-Ca-Key"; -} \ No newline at end of file diff --git a/quick-wx-api/src/main/resources/application.properties b/quick-wx-api/src/main/resources/application.properties deleted file mode 100644 index 5c4f0716..00000000 --- a/quick-wx-api/src/main/resources/application.properties +++ /dev/null @@ -1,5 +0,0 @@ - -server.port=8080 - -logging.config=classpath:log4j2.xml -#logging.level.root=error \ No newline at end of file diff --git a/quick-wx-api/src/main/resources/log4j2.xml b/quick-wx-api/src/main/resources/log4j2.xml deleted file mode 100644 index c6838595..00000000 --- a/quick-wx-api/src/main/resources/log4j2.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/quick-ElasticSearch/pom.xml b/quick-wx-public/pom.xml similarity index 50% rename from quick-ElasticSearch/pom.xml rename to quick-wx-public/pom.xml index efda04d5..542d7d7e 100644 --- a/quick-ElasticSearch/pom.xml +++ b/quick-wx-public/pom.xml @@ -2,73 +2,111 @@ - quick-es - com.quick + quick-wx-public 1.0-SNAPSHOT 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 1.3.2.RELEASE - + quick-platform + com.quick + 1.0-SNAPSHOT + ../quick-platform/pom.xml - - 5.4.0 - 5.1.30 - org.springframework.boot spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - - - org.springframework.boot - spring-boot-starter-log4j + org.springframework.boot spring-boot-starter-test test + org.mybatis.spring.boot mybatis-spring-boot-starter - 1.1.1 - org.elasticsearch - elasticsearch - ${es.version} + mysql + mysql-connector-java + + + + com.alibaba + druid-spring-boot-starter - + - org.elasticsearch.client - transport - ${es.version} + org.springframework.boot + spring-boot-starter-actuator + - org.apache.logging.log4j - log4j-api - 2.8.2 + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.alibaba + fastjson + + + + org.apache.httpcomponents + httpclient + + + + org.apache.httpcomponents + httpmime + + + - org.apache.logging.log4j - log4j-core - 2.8.2 + org.apache.commons + commons-lang3 + + + + commons-io + commons-io + + + + com.thoughtworks.xstream + xstream + + + + + org.dom4j + dom4j + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + org.springframework.boot spring-boot-maven-plugin @@ -77,7 +115,6 @@ org.mybatis.generator mybatis-generator-maven-plugin - 1.3.2 true true @@ -90,16 +127,12 @@ - - org.springframework.boot - spring-boot-maven-plugin - + org.apache.maven.plugins - maven-compiler-plugin + maven-surefire-plugin - 1.8 - 1.8 + true diff --git a/quick-wx-public/src/main/java/com/wx/pn/VoiceApplication.java b/quick-wx-public/src/main/java/com/wx/pn/VoiceApplication.java new file mode 100644 index 00000000..2c8e2aee --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/VoiceApplication.java @@ -0,0 +1,41 @@ +package com.wx.pn; + +import com.wx.pn.api.config.ApiConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableAsync; + +/** + * @author vector + * @date: 2018/11/2 0002 18:23 + */ +@SpringBootApplication +@EnableCaching +@EnableAsync +public class VoiceApplication { + + + @Value("${weixin.app-id}") + private String appKey; + + @Value("${weixin.app-secret}") + private String appSecret; + + @Value("${weixin.token}") + private String token; + + + @Bean(name = "wxApiConfig") + public ApiConfig initWX() { + return new ApiConfig(appKey, appSecret, token); + } + + + public static void main(String[] args) { + SpringApplication.run(VoiceApplication.class, args); + } +} + diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/BaseApi.java b/quick-wx-public/src/main/java/com/wx/pn/api/BaseApi.java new file mode 100644 index 00000000..91b944af --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/BaseApi.java @@ -0,0 +1,48 @@ +package com.wx.pn.api; + +import com.wx.pn.api.enums.ResultType; +import com.wx.pn.api.model.BaseResponse; +import com.wx.pn.api.utils.BeanUtil; +import com.wx.pn.api.utils.NetWorkCenter; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author vector + * @date: 2018/11/5 0005 17:58 + */ +public abstract class BaseApi { + protected static final String BASE_API_URL = "https://api.weixin.qq.com/"; + + protected BaseResponse executeGet(String url) { + BaseResponse response; + BeanUtil.requireNonNull(url, "url is null"); + response = NetWorkCenter.get(url); + return response; + } + + protected BaseResponse executePost(String url, String json, File file) { + BaseResponse response; + BeanUtil.requireNonNull(url, "url is null"); + List files = null; + if (null != file) { + files = new ArrayList() {{ + add(file); + }}; + } + response = NetWorkCenter.post(url, json, files); + return response; + } + + + protected BaseResponse executePost(String url, String json) { + return executePost(url, json, null); + } + + protected boolean isSuccess(String errCode) { + return ResultType.SUCCESS.getCode().toString().equals(errCode); + } +} + diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/MediaApi.java b/quick-wx-public/src/main/java/com/wx/pn/api/MediaApi.java new file mode 100644 index 00000000..038279f0 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/MediaApi.java @@ -0,0 +1,120 @@ +package com.wx.pn.api; + +import com.alibaba.fastjson.JSON; +import com.wx.pn.api.config.ApiConfig; +import com.wx.pn.api.enums.MediaType; +import com.wx.pn.api.enums.ResultType; +import com.wx.pn.api.model.BaseResponse; +import com.wx.pn.api.response.DownloadMediaResponse; +import com.wx.pn.api.response.UploadMediaResponse; +import com.wx.pn.api.utils.NetWorkCenter; +import com.wx.pn.api.utils.StreamUtil; +import org.apache.http.Header; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author vector + * @date: 2018/11/9 0009 11:29 + */ +public class MediaApi extends BaseApi { + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + private ApiConfig apiConfig; + + public MediaApi(ApiConfig apiConfig) { + this.apiConfig = apiConfig; + } + + /** + * 上传资源,会在微信服务器上保存3天,之后会被删除 + * + * @param type 资源类型 + * @param file 需要上传的文件 + * @return 响应对象 + */ + public UploadMediaResponse uploadMedia(MediaType type, File file) { + if (Objects.isNull(file)) { + return null; + } + UploadMediaResponse response; + String url = + String.format("http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s", apiConfig.getAccessToken(), type.toString()); + List files = new ArrayList() {{ + add(file); + }}; + BaseResponse resp = NetWorkCenter.post(url, null, files); + ResultType resultType = ResultType.get(resp.getErrcode()); + if (!Objects.equals(resultType, ResultType.SUCCESS)) { + logger.error("file [{}] uploadMedia err: {}", file.getName(), resultType.getDescription()); + return null; + } + response = JSON.parseObject(resp.getErrmsg(), UploadMediaResponse.class); + return response; + } + + /** + * 下载资源 + * + * @param mediaId 微信提供的资源唯一标识 + * @return 响应对象 + */ + public DownloadMediaResponse downloadMedia(String mediaId) { + String url = String.format("http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s", apiConfig.getAccessToken(), mediaId); + return download(url); + } + + /** + * 下载文件通用方法 + * + * @param url + * @return + */ + public DownloadMediaResponse download(String url) { + RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(NetWorkCenter.CONNECT_TIMEOUT).setConnectTimeout(NetWorkCenter.CONNECT_TIMEOUT).setSocketTimeout(NetWorkCenter.CONNECT_TIMEOUT).build(); + CloseableHttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(config).build(); + HttpGet get = new HttpGet(url); + DownloadMediaResponse response = new DownloadMediaResponse(); + try { + CloseableHttpResponse r = client.execute(get); + if (HttpStatus.SC_OK == r.getStatusLine().getStatusCode()) { + InputStream inputStream = r.getEntity().getContent(); + Header[] headers = r.getHeaders("Content-disposition"); + if (null != headers && 0 != headers.length) { + Header length = r.getHeaders("Content-Length")[0]; + response.setContent(inputStream, Integer.valueOf(length.getValue())); + response.setFileName(headers[0].getElements()[0].getParameterByName("filename").getValue()); + } else { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StreamUtil.copy(inputStream, out); + String json = out.toString(); + response = JSON.parseObject(json, DownloadMediaResponse.class); + } + } + } catch (IOException e) { + logger.error("IO处理异常", e); + } finally { + try { + client.close(); + } catch (IOException e) { + logger.error("异常", e); + } + } + return response; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/MenuApi.java b/quick-wx-public/src/main/java/com/wx/pn/api/MenuApi.java new file mode 100644 index 00000000..c85a1e19 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/MenuApi.java @@ -0,0 +1,61 @@ +package com.wx.pn.api; + +import com.wx.pn.api.config.ApiConfig; +import com.wx.pn.api.enums.ResultType; +import com.wx.pn.api.model.BaseResponse; +import com.wx.pn.api.model.Menu; +import com.wx.pn.api.utils.BeanUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author vector + * @date: 2018/11/8 0008 17:09 + *

+ * 菜单的相关操作 + */ +public class MenuApi extends BaseApi { + private static final Logger logger = LoggerFactory.getLogger(MenuApi.class); + + private ApiConfig apiConfig; + + public MenuApi(ApiConfig apiConfig) { + this.apiConfig = apiConfig; + } + + /** + * 创建菜单 + * + * @param menu + * @return + */ + public ResultType createMenu(Menu menu) { + BeanUtil.requireNonNull(menu, "menu is null"); + String url = BASE_API_URL; + if (BeanUtil.isNull(menu.getMatchrule())) { + //普通菜单 + logger.info("创建普通菜单....."); + url += "cgi-bin/menu/create?access_token=" + apiConfig.getAccessToken(); + } else { + //个性化菜单 + logger.info("创建个性化菜单....."); + url += "cgi-bin/menu/addconditional?access_token=" + apiConfig.getAccessToken(); + } + BaseResponse baseResponse = executePost(url, menu.toJsonString()); + return ResultType.get(baseResponse.getErrcode()); + } + + /** + * 删除所有菜单,包括个性化菜单 + * + * @return 调用结果 + */ + public ResultType deleteMenu() { + logger.debug("删除菜单....."); + String url = BASE_API_URL + "cgi-bin/menu/delete?access_token=" + apiConfig.getAccessToken(); + BaseResponse response = executeGet(url); + return ResultType.get(response.getErrcode()); + } + + +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/TemplateMsgApi.java b/quick-wx-public/src/main/java/com/wx/pn/api/TemplateMsgApi.java new file mode 100644 index 00000000..d2193464 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/TemplateMsgApi.java @@ -0,0 +1,86 @@ +package com.wx.pn.api; + +import com.alibaba.fastjson.JSON; +import com.wx.pn.api.config.ApiConfig; +import com.wx.pn.api.enums.ResultType; +import com.wx.pn.api.model.BaseResponse; +import com.wx.pn.api.model.Industry; +import com.wx.pn.api.model.TemplateMsg; +import com.wx.pn.api.response.AddTemplateResponse; +import com.wx.pn.api.response.SendTemplateResponse; +import com.wx.pn.api.utils.BeanUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author vector + * @date: 2018/11/16 0016 10:58 + */ +public class TemplateMsgApi extends BaseApi { + + private static final Logger LOG = LoggerFactory.getLogger(TemplateMsgApi.class); + + + private ApiConfig apiConfig; + + public TemplateMsgApi(ApiConfig apiConfig) { + this.apiConfig = apiConfig; + } + + /** + * 设置行业 + * + * @param industry 行业参数 + * @return 操作结果 + */ + public ResultType setIndustry(Industry industry) { + LOG.debug("设置行业......"); + BeanUtil.requireNonNull(industry, "行业对象为空"); + String url = BASE_API_URL + "cgi-bin/template/api_set_industry?access_token=" + apiConfig.getAccessToken(); + BaseResponse response = executePost(url, industry.toJsonString()); + return ResultType.get(response.getErrcode()); + } + + /** + * 添加模版 + * + * @param shortTemplateId 模版短id + * @return 操作结果 + */ + public AddTemplateResponse addTemplate(String shortTemplateId) { + LOG.debug("添加模版......"); + BeanUtil.requireNonNull(shortTemplateId, "短模版id必填"); + String url = BASE_API_URL + "cgi-bin/template/api_add_template?access_token=" + apiConfig.getAccessToken(); + ; + Map params = new HashMap(); + params.put("template_id_short", shortTemplateId); + BaseResponse r = executePost(url, JSON.toJSONString(params)); + String resultJson = isSuccess(r.getErrcode()) ? r.getErrmsg() : r.toJsonString(); + return JSON.parseObject(resultJson, AddTemplateResponse.class); + } + + /** + * 发送模版消息 + * + * @param msg 消息 + * @return 发送结果 + */ + public SendTemplateResponse send(TemplateMsg msg) { + LOG.debug("发送模版消息......"); + BeanUtil.requireNonNull(msg.getTouser(), "openid is null"); + BeanUtil.requireNonNull(msg.getTemplateId(), "template_id is null"); + BeanUtil.requireNonNull(msg.getData(), "data is null"); +// BeanUtil.requireNonNull(msg.getTopcolor(), "top color is null"); +// BeanUtil.requireNonNull(msg.getUrl(), "url is null"); + String url = BASE_API_URL + "cgi-bin/message/template/send?access_token=" + apiConfig.getAccessToken(); + ; + BaseResponse r = executePost(url, msg.toJsonString()); + String resultJson = isSuccess(r.getErrcode()) ? r.getErrmsg() : r.toJsonString(); + SendTemplateResponse result = JSON.parseObject(resultJson, SendTemplateResponse.class); + return result; + } + +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/UserInfoApi.java b/quick-wx-public/src/main/java/com/wx/pn/api/UserInfoApi.java new file mode 100644 index 00000000..a6cd1860 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/UserInfoApi.java @@ -0,0 +1,33 @@ +package com.wx.pn.api; + + +import com.wx.pn.api.config.ApiConfig; +import com.wx.pn.api.enums.ResultType; +import com.wx.pn.api.exception.WeixinException; +import com.wx.pn.api.model.BaseResponse; + +import java.util.Objects; + +/** + * @author vector + * @date: 2018/11/5 0005 17:54 + */ +public class UserInfoApi extends BaseApi { + + /** + * 获取用户信息(微信中) + * @param config + * @param openId + * @return + */ + public String getUsers(ApiConfig config, String openId) { + String url = String.format(BASE_API_URL + "cgi-bin/user/info?access_token=%s&lang=zh_CN&openid=%s", config.getAccessToken(), openId); + BaseResponse baseResponse = executeGet(url); + if (Objects.equals(ResultType.get(baseResponse.getErrcode()), ResultType.SUCCESS)) { + return baseResponse.getErrmsg(); + }else{ + throw new WeixinException("获取用户信息失败"); + } + + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/config/ApiConfig.java b/quick-wx-public/src/main/java/com/wx/pn/api/config/ApiConfig.java new file mode 100644 index 00000000..f341ec00 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/config/ApiConfig.java @@ -0,0 +1,231 @@ +package com.wx.pn.api.config; + +import com.alibaba.fastjson.JSON; +import com.wx.pn.api.enums.ChangeType; +import com.wx.pn.api.exception.WeixinException; +import com.wx.pn.api.handler.ApiConfigChangeHandle; +import com.wx.pn.api.handler.GetTokenResponse; +import com.wx.pn.api.response.GetJsApiTicketResponse; +import com.wx.pn.api.utils.NetWorkCenter; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.Observable; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class ApiConfig extends Observable implements Serializable { + + + private static final Logger LOG = LoggerFactory.getLogger(ApiConfig.class); + /** + * 这里定义token正在刷新的标识,想要达到的目标是当有一个请求来获取token,发现token已经过期(我这里的过期逻辑是比官方提供的早100秒),然后开始刷新token + * 在刷新的过程里,如果又继续来获取token,会先把旧的token返回,直到刷新结束,之后再来的请求,将获取到新的token + * 利用AtomicBoolean实现原理: + * 当请求来的时候,检查token是否已经过期(7100秒)以及标识是否已经是true(表示已经在刷新了,还没刷新完),过期则将此标识设为true,并开始刷新token + * 在刷新结束前再次进来的请求,由于标识一直是true,而会直接拿到旧的token,由于我们的过期逻辑比官方的早100秒,所以旧的还可以继续用 + * 无论刷新token正在结束还是出现异常,都在最后将标识改回false,表示刷新工作已经结束 + */ + private final AtomicBoolean tokenRefreshing = new AtomicBoolean(false); + private final AtomicBoolean jsRefreshing = new AtomicBoolean(false); + + private final String appid; + private final String secret; + private final String token; + private String accessToken; + private String jsApiTicket; + private boolean enableJsApi; + private long jsTokenStartTime; + private long weixinTokenStartTime; + + + + /** + * 构造方法一,实现同时获取access_token。不启用jsApi + * + * @param appid 公众号appid + * @param secret 公众号secret + */ + public ApiConfig(String appid, String secret, String token) { + this(appid, secret, token,false); + } + + /** + * 构造方法二,实现同时获取access_token,启用jsApi + * + * @param appid 公众号appid + * @param secret 公众号secret + * @param enableJsApi 是否启动js api + */ + public ApiConfig(String appid, String secret, String token, boolean enableJsApi) { + this.appid = appid; + this.secret = secret; + this.token = token; + this.enableJsApi = enableJsApi; + long now = System.currentTimeMillis(); + initToken(now); + if (enableJsApi) + initJSToken(now); + } + + public String getAppid() { + return appid; + } + + public String getSecret() { + return secret; + } + + /** + * 注意此token与accessToken不一样 + * @return + */ + public String getToken() { + return this.token; + } + + public String getAccessToken() { + long now = System.currentTimeMillis(); + long time = now - this.weixinTokenStartTime; + try { + /* + * 判断优先顺序: + * 1.官方给出的超时时间是7200秒,这里用7100秒来做,防止出现已经过期的情况 + * 2.刷新标识判断,如果已经在刷新了,则也直接跳过,避免多次重复刷新,如果没有在刷新,则开始刷新 + */ + if (time > 7100000 && this.tokenRefreshing.compareAndSet(false, true)) { + LOG.debug("准备刷新token............."); + initToken(now); + } + } catch (Exception e) { + LOG.warn("刷新Token出错.", e); + //刷新工作出现有异常,将标识设置回false + this.tokenRefreshing.set(false); + } + return accessToken; + } + + public String getJsApiTicket() { + if (enableJsApi) { + long now = System.currentTimeMillis(); + try { + //官方给出的超时时间是7200秒,这里用7100秒来做,防止出现已经过期的情况 + if (now - this.jsTokenStartTime > 7100000 && this.jsRefreshing.compareAndSet(false, true)) { + getAccessToken(); + initJSToken(now); + } + } catch (Exception e) { + LOG.warn("刷新jsTicket出错.", e); + //刷新工作出现有异常,将标识设置回false + this.jsRefreshing.set(false); + } + } else { + jsApiTicket = null; + } + return jsApiTicket; + } + + public boolean isEnableJsApi() { + return enableJsApi; + } + + public void setEnableJsApi(boolean enableJsApi) { + this.enableJsApi = enableJsApi; + if (!enableJsApi) + this.jsApiTicket = null; + } + + /** + * 添加配置变化监听器 + * + * @param handle 监听器 + */ + public void addHandle(final ApiConfigChangeHandle handle) { + super.addObserver(handle); + } + + /** + * 移除配置变化监听器 + * + * @param handle 监听器 + */ + public void removeHandle(final ApiConfigChangeHandle handle) { + super.deleteObserver(handle); + } + + /** + * 移除所有配置变化监听器 + */ + public void removeAllHandle() { + super.deleteObservers(); + } + + /** + * 初始化微信配置,即第一次获取access_token + * + * @param refreshTime 刷新时间 + */ + private void initToken(final long refreshTime) { + LOG.debug("开始初始化access_token........"); + //记住原本的时间,用于出错回滚 + final long oldTime = this.weixinTokenStartTime; + this.weixinTokenStartTime = refreshTime; + String url = + "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + this.appid + "&secret=" + + this.secret; + NetWorkCenter.get(url, null, (resultCode, resultJson) -> { + if (HttpStatus.SC_OK == resultCode) { + GetTokenResponse response = JSON.parseObject(resultJson, GetTokenResponse.class); + LOG.debug("获取access_token:{}", response.getAccessToken()); + if (null == response.getAccessToken()) { + //刷新时间回滚 + weixinTokenStartTime = oldTime; + throw new WeixinException( + "微信公众号token获取出错,错误信息:" + response.getErrcode() + "," + response.getErrmsg()); + } + accessToken = response.getAccessToken(); + //设置通知点 + setChanged(); + notifyObservers(new ConfigChangeNotice(appid, ChangeType.ACCESS_TOKEN, accessToken)); + } + }); + //刷新工作做完,将标识设置回false + this.tokenRefreshing.set(false); + } + + /** + * 初始化微信JS-SDK,获取JS-SDK token + * + * @param refreshTime 刷新时间 + */ + private void initJSToken(final long refreshTime) { + LOG.debug("初始化 jsapi_ticket........"); + //记住原本的时间,用于出错回滚 + final long oldTime = this.jsTokenStartTime; + this.jsTokenStartTime = refreshTime; + String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken + "&type=jsapi"; + NetWorkCenter.get(url, null, new NetWorkCenter.ResponseCallback() { + @Override + public void onResponse(int resultCode, String resultJson) { + if (HttpStatus.SC_OK == resultCode) { + GetJsApiTicketResponse response = JSON.parseObject(resultJson, GetJsApiTicketResponse.class); + LOG.debug("获取jsapi_ticket:{}", response.getTicket()); + if (StringUtils.isBlank(response.getTicket())) { + //刷新时间回滚 + jsTokenStartTime = oldTime; + throw new WeixinException( + "微信公众号jsToken获取出错,错误信息:" + response.getErrcode() + "," + response.getErrmsg()); + } + jsApiTicket = response.getTicket(); + //设置通知点 + setChanged(); + notifyObservers(new ConfigChangeNotice(appid, ChangeType.JS_TOKEN, jsApiTicket)); + } + } + }); + //刷新工作做完,将标识设置回false + this.jsRefreshing.set(false); + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/config/ConfigChangeNotice.java b/quick-wx-public/src/main/java/com/wx/pn/api/config/ConfigChangeNotice.java new file mode 100644 index 00000000..9099aee6 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/config/ConfigChangeNotice.java @@ -0,0 +1,70 @@ +package com.wx.pn.api.config; + +import com.wx.pn.api.enums.ChangeType; +import com.wx.pn.api.model.BaseModel; + +import java.util.Date; + +public final class ConfigChangeNotice extends BaseModel { + + private Date noticeTime; + + private String appid; + + private ChangeType type; + + private String value; + + public ConfigChangeNotice() { + this.noticeTime = new Date(); + } + + public ConfigChangeNotice(String appid, ChangeType type, String value) { + this(); + this.appid = appid; + this.type = type; + this.value = value; + } + + public String getAppid() { + return appid; + } + + public void setAppid(String appid) { + this.appid = appid; + } + + public ChangeType getType() { + return type; + } + + public void setType(ChangeType type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Date getNoticeTime() { + return noticeTime; + } + + public void setNoticeTime(Date noticeTime) { + this.noticeTime = noticeTime; + } + + @Override + public String toString() { + return "ConfigChangeNotice{" + + "noticeTime=" + noticeTime + + ", appid='" + appid + '\'' + + ", type=" + type + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/enums/ChangeType.java b/quick-wx-public/src/main/java/com/wx/pn/api/enums/ChangeType.java new file mode 100644 index 00000000..71dbf1de --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/enums/ChangeType.java @@ -0,0 +1,13 @@ +package com.wx.pn.api.enums; + +public enum ChangeType { + /** + * 微信token + */ + ACCESS_TOKEN, + + /** + * 微信js token + */ + JS_TOKEN +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/enums/MediaType.java b/quick-wx-public/src/main/java/com/wx/pn/api/enums/MediaType.java new file mode 100644 index 00000000..aa9729da --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/enums/MediaType.java @@ -0,0 +1,40 @@ +package com.wx.pn.api.enums; + +public enum MediaType { + + /** + * 图片 + */ + IMAGE("image"), + + /** + * 语音 + */ + VOICE("voice"), + + /** + * 视频 + */ + VIDEO("video"), + + /** + * 缩略图 + */ + THUMB("thumb"), + + /** + * 图文消息 + */ + NEWS("news"); + + String value; + + MediaType(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/enums/MenuType.java b/quick-wx-public/src/main/java/com/wx/pn/api/enums/MenuType.java new file mode 100644 index 00000000..345e9c68 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/enums/MenuType.java @@ -0,0 +1,70 @@ +package com.wx.pn.api.enums; + +public enum MenuType { + /** + * 点击推事件 + */ + CLICK("click"), + + /** + * 跳转URL + */ + VIEW("view"), + + /*-------------------------以下仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户------------------------*/ + + /** + * 扫码推事件 + */ + SCANCODE_PUSH("scancode_push"), + + /** + * 扫码推事件且弹出“消息接收中”提示框 + */ + SCANCODE_WAITMSG("scancode_waitmsg"), + + /** + * 弹出系统拍照发图 + */ + PIC_SYSPHOTO("pic_sysphoto"), + + /** + * 弹出拍照或者相册发图 + */ + PIC_PHOTO_OR_ALBUM("pic_photo_or_album"), + + /** + * 弹出微信相册发图器 + */ + PIC_WEIXIN("pic_weixin"), + + /** + * 弹出地理位置选择器 + */ + LOCATION_SELECT("location_select"), + + + /*-----------------------------以下专门给第三方平台旗下未微信认证(具体而言,是资质认证未通过)的订阅号准备的事件类型,它们是没有事件推送的,能力相对受限,其他类型的公众号不必使用--------------------------*/ + + /** + * 下发消息(除文本消息) + */ + MEDIA_ID("media_id"), + + /** + * 跳转图文消息URL + */ + VIEW_LIMITED("view_limited"); + + String value; + + MenuType(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/enums/ResultType.java b/quick-wx-public/src/main/java/com/wx/pn/api/enums/ResultType.java new file mode 100644 index 00000000..158ae794 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/enums/ResultType.java @@ -0,0 +1,626 @@ +package com.wx.pn.api.enums; + + +import com.wx.pn.api.utils.BeanUtil; + +public enum ResultType { + /** + * 系统繁忙 + */ + SYSTEM_BUSY(-1, "系统繁忙"), + + /** + * 请求成功 + */ + SUCCESS(0, "请求成功"), + + /** + * 获取access_token时AppSecret错误,或者access_token无效 + */ + APP_SECRET_ERROR(40001, "获取access_token时AppSecret错误,或者access_token无效"), + + /** + * 不合法的凭证类型 + */ + ILLEGAL_TOKEN_TYPE(40002, "不合法的凭证类型"), + + /** + * 不合法的OpenID + */ + ILLEGAL_OPEN_ID(40003, "不合法的OpenID"), + + /** + * 不合法的媒体文件类型 + */ + ILLEGAL_MEDIA_TYPE(40004, "不合法的媒体文件类型"), + + /** + * 不合法的文件类型 + */ + ILLEGAL_FILE_TYPE(40005, "不合法的文件类型"), + + /** + * 不合法的文件大小 + */ + ILLEGAL_FILE_SIZE(40006, "不合法的文件大小"), + + /** + * 不合法的媒体文件id + */ + ILLEGAL_MEDIA_ID(40007, "不合法的媒体文件id"), + + /** + * 不合法的消息类型 + */ + ILLEGAL_MESSAGE_TYPE(40008, "不合法的消息类型"), + + /** + * 不合法的图片文件大小 + */ + ILLEGAL_PICTURE_SIZE(40009, "不合法的图片文件大小"), + + /** + * 不合法的语音文件大小 + */ + ILLEGAL_VOICE_SIZE(40010, "不合法的语音文件大小"), + + /** + * 不合法的视频文件大小 + */ + ILLEGAL_VIDEO_SIZE(40011, "不合法的视频文件大小"), + + /** + * 不合法的缩略图文件大小 + */ + ILLEGAL_THUMBNAIL_SIZE(40012, "不合法的缩略图文件大小"), + + /** + * 不合法的APPID + */ + ILLEGAL_APP_ID(40013, "不合法的APPID"), + + /** + * 不合法的access_token + */ + ILLEGAL_ACCESS_TOKEN(40014, "不合法的access_token"), + + /** + * 不合法的菜单类型 + */ + ILLEGAL_MENU_TYPE(40015, "不合法的菜单类型"), + + /** + * 不合法的按钮个数 + */ + ILLEGAL_MENU_NUMBER(40016, "不合法的按钮个数"), + + /** + * 不合法的按钮名字长度 + */ + ILLEGAL_BUTTON_NAME_LENTH(40018, "不合法的按钮名字长度"), + + /** + * 不合法的按钮KEY长度 + */ + ILLEGAL_BUTTON_KEY_LENTH(40019, "不合法的按钮KEY长度"), + + /** + * 不合法的按钮URL长度 + */ + ILLEGAL_BUTTON_URL_LENTH(40020, "不合法的按钮URL长度"), + + /** + * 不合法的菜单版本号 + */ + ILLEGAL_MENU_VERSION(40021, "不合法的菜单版本号"), + + /** + * 不合法的子菜单级数 + */ + ILLEGAL_SUB_MENU_LEVEL(40022, "不合法的子菜单级数"), + + /** + * 不合法的子菜单按钮个数 + */ + ILLEGAL_SUB_MENU_NUMBER(40023, "不合法的子菜单按钮个数"), + + /** + * 不合法的子菜单按钮类型 + */ + ILLEGAL_SUB_MENU_TYPE(40024, "不合法的子菜单按钮类型"), + + /** + * 不合法的子菜单按钮名字长度 + */ + ILLEGAL_SUB_MENU_LENTH(40025, "不合法的子菜单按钮名字长度"), + + /** + * 不合法的子菜单按钮KEY长度 + */ + ILLEGAL_SUB_MENU_KEY_LENTH(40026, "不合法的子菜单按钮KEY长度"), + + /** + * 不合法的子菜单按钮URL长度 + */ + ILLEGAL_SUB_MENU_URL_LENTH(40027, "不合法的子菜单按钮URL长度"), + + /** + * 不合法的自定义菜单使用用户 + */ + ILLEGAL_MENU_USER(40028, "不合法的自定义菜单使用用户"), + + /** + * 不合法的oauth_code + */ + ILLEGAL_OAUTH_CODE(40029, "不合法的oauth_code"), + + /** + * 不合法的refresh_token + */ + ILLEGAL_REFRESH_TOKEN(40030, "不合法的refresh_token"), + + /** + * 不合法的openid列表 + */ + ILLEGAL_OPENID_LIST(40031, "不合法的openid列表"), + + /** + * 不合法的openid列表长度 + */ + ILLEGAL_OPENID_LIST_LENTH(40032, "不合法的openid列表长度"), + + /** + * 不合法的请求字符 + */ + ILLEGAL_REQUEST_STRING(40033, "不合法的请求字符"), + + /** + * 不合法的参数 + */ + ILLEGAL_PARAM(40035, "不合法的参数"), + + /** + * 不合法的请求格式 + */ + ILLEGAL_REQUEST_TYPE(40038, "不合法的请求格式"), + + /** + * 不合法的URL长度 + */ + ILLEGAL_URL_LENTH(40039, "不合法的URL长度"), + + /** + * 不合法的分组id + */ + ILLEGAL_GROUP_ID(40050, "不合法的分组id"), + + /** + * 分组名字不合法 + */ + ILLEGAL_GROUP_NAME(40051, "分组名字不合法"), + + /** + * 分组名字不合法 + */ + INVALID_BUTTON_URL_DOMAIN(40055, "按钮URL域名错误"), + + /** + * media_id大小不合法 + */ + ILLEGAL_MEDIA_ID_SIZE(40118, "media_id大小不合法"), + + /** + * please don't contain other home page url hint: [seuyba01071891] + */ + URL_CONTAIN_OTHER_HOME_PAGE(40155, "please don't contain other home page url hint: [seuyba01071891]"), + + + /** + * button类型错误 + */ + BUTTON_TYPE_ERROR(40119, "button类型错误"), + + /** + * 不合法的media_id类型 + */ + ILLEGAL_MEDIA_ID_TYPE(40121, "不合法的media_id类型"), + + /** + * 缺少access_token参数 + */ + NO_ACCESS_TOKEN(41001, "缺少access_token参数"), + + /** + * 缺少appid参数 + */ + NO_APPID(41002, "缺少appid参数"), + + /** + * 缺少refresh_token参数 + */ + NO_REFRESH_TOKEN(41003, "缺少refresh_token参数"), + + /** + * 缺少secret参数 + */ + NO_SECRET(41004, "缺少secret参数"), + + /** + * 缺少多媒体文件数据 + */ + NO_MEDIA_DATA(41005, "缺少多媒体文件数据"), + + /** + * 缺少media_id参数 + */ + NO_MEDIA_ID(41006, "缺少media_id参数"), + + /** + * 缺少子菜单数据 + */ + NO_SUB_MENU_DATA(41007, "缺少子菜单数据"), + + /** + * 缺少oauth code + */ + NO_OAUTH_CODE(41008, "缺少oauth code"), + + /** + * 缺少openid + */ + NO_OPEN_ID(41009, "缺少openid"), + + /** + * access_token超时 + */ + ACCESS_TOKEN_TIMEOUT(42001, "access_token超时"), + + /** + * refresh_token超时 + */ + REFRESH_TOKEN_TIMEOUT(42002, "refresh_token超时"), + + /** + * oauth_code超时 + */ + OAUTH_CODE_TIMEOUT(42003, "oauth_code超时"), + + /** + * 需要GET请求 + */ + NEED_REQUEST_GET(43001, "需要GET请求"), + + /** + * 需要POST请求 + */ + NEED_REQUEST_POST(43002, "需要POST请求"), + + /** + * 需要HTTPS请求 + */ + NEED_REQUEST_HTTPS(43003, "需要HTTPS请求"), + + /** + * 需要接收者关注 + */ + NEED_USER_FOLLOW(43004, "需要接收者关注"), + + /** + * 需要好友关系 + */ + NEED_FRIEND(43005, "需要好友关系"), + + /** + * 多媒体文件为空 + */ + MEDIA_FILE_IS_NULL(44001, "多媒体文件为空"), + + /** + * POST的数据包为空 + */ + POST_DATA_IS_NULL(44002, "POST的数据包为空"), + + /** + * 图文消息内容为空 + */ + NEWS_MESSAGE_IS_NULL(44003, "图文消息内容为空"), + + /** + * 文本消息内容为空 + */ + TEXT_MESSAGE_IS_NULL(44004, "文本消息内容为空"), + + /** + * 多媒体文件大小超过限制 + */ + MEDIA_DATA_OVER_LIMIT(45001, "多媒体文件大小超过限制"), + + /** + * 消息内容超过限制 + */ + MESSAGE_CONTENT_OVER_LIMIT(45002, "消息内容超过限制"), + + /** + * 标题字段超过限制 + */ + TITLE_OVER_LIMIT(45003, "标题字段超过限制"), + + /** + * 描述字段超过限制 + */ + DESCRIPTION_OVER_LIMIT(45004, "描述字段超过限制"), + + /** + * 链接字段超过限制 + */ + LINK_OVER_LIMIT(45005, "链接字段超过限制"), + + /** + * 图片链接字段超过限制 + */ + PICTURE_LINK_OVER_LIMIT(45006, "图片链接字段超过限制"), + + /** + * 语音播放时间超过限制 + */ + VOICE_TIME_OVER_LIMIT(45007, "语音播放时间超过限制"), + + /** + * 图文消息超过限制 + */ + NEWS_MESSAGE_OVER_LIMIT(45008, "图文消息超过限制"), + + /** + * 接口调用超过限制 + */ + INTERFACE_OVER_LIMIT(45009, "接口调用超过限制"), + + /** + * 创建菜单个数超过限制 + */ + MENU_OVER_LIMIT(45010, "创建菜单个数超过限制"), + + /** + * 回复时间超过限制 + */ + REVIEW_TIME_OVER_LIMIT(45015, "回复时间超过限制"), + + /** + * 系统分组,不允许修改 + */ + NO_MODIFY_SYSTEM_GROUP(45016, "系统分组,不允许修改"), + + /** + * 分组名字过长 + */ + GROUP_NAME_TOO_LONG(45017, "分组名字过长"), + + /** + * 分组数量超过上限 + */ + GROUP_COUNT_TOO_MANY(45018, "分组数量超过上限"), + + /** + * 客服接口下行条数超过上限 + */ + CUSTOMER_SERVICE_DOWN_TOO_MANY(45047, "客服接口下行条数超过上限"), + + /** + * 创建的标签数过多,请注意不能超过100个 + */ + TAG_NAME_TOO_MANY(45056, "创建的标签数过多,请注意不能超过100个"), + + /** + * 该标签下粉丝数超过10w,不允许直接删除 + */ + CAN_NOT_DELETE_TAG(45057, "该标签下粉丝数超过10w,不允许直接删除"), + + /** + * 不能修改0/1/2这三个系统默认保留的标签 + */ + CAN_NOT_MODIFY_TAG(45058, "不能修改0/1/2这三个系统默认保留的标签"), + + /** + * 有粉丝身上的标签数已经超过限制 + */ + FANS_TAGS_TOO_MANY(45059, "有粉丝身上的标签数已经超过限制"), + + /** + * 标签名非法,请注意不能和其他标签重名 + */ + ILLEGAL_TAG_NAME(45157, "标签名非法,请注意不能和其他标签重名"), + + /** + * 标签名长度超过30个字节 + */ + TAG_NAME_TOO_LONG(45158, "标签名长度超过30个字节"), + + /** + * 非法的tag_id + */ + ILLEGAL_TAG_ID(45159, "非法的tag_id"), + + /** + * 不存在媒体数据 + */ + NOT_EXIST_MEDIA_DATA(46001, "不存在媒体数据"), + + /** + * 不存在的菜单版本 + */ + NOT_EXIST_MENU_VERSION(46002, "不存在的菜单版本"), + + /** + * 不存在的菜单数据 + */ + NOT_EXIST_MENU_DATA(46003, "不存在的菜单数据"), + + /** + * 不存在的用户 + */ + NOT_EXIST_USER(46004, "不存在的用户"), + + /** + * 解析JSON/XML内容错误 + */ + JSON_OR_XML_ERROR(47001, "解析JSON/XML内容错误"), + + /** + * api功能未授权 + */ + API_NOT_ALLOW_CALL(48001, "api功能未授权"), + + /** + * 传入的openid不属于此AppID + */ + OPENID_APPID_NOT_MATCH(49003, "传入的openid不属于此AppID"), + + /** + * 用户未授权该api + */ + USER_NOT_ALLOW_API(50001, "用户未授权该api"), + + /** + * 用户受限,可能是违规后接口被封禁 + */ + USER_USE_LIMIT(50002, "用户受限,可能是违规后接口被封禁"), + + /** + * 参数错误(invalid parameter) + */ + INVALID_PARAM(61451, "参数错误(invalid parameter)"), + + /** + * 无效客服账号(invalid kf_account) + */ + INVALID_ACCOUNT(61452, "无效客服账号(invalid kf_account)"), + + /** + * 客服帐号已存在(kf_account existed) + */ + ACCOUNT_EXISTS(61453, "客服帐号已存在(kf_account existed)"), + + /** + * 客服帐号名长度超过限制(仅允许10个英文字符,不包括@及@后的公众号的微信号)(invalid kf_acount length) + */ + ACCOUNT_TOO_LONG(61454, "客服帐号名长度超过限制(仅允许10个英文字符,不包括@及@后的公众号的微信号)(invalid kf_acount length)"), + + /** + * 客服帐号名包含非法字符(仅允许英文+数字)(illegal character in kf_account) + */ + ILLEGAL_ACCOUNT_CHARACTER(61455, "客服帐号名包含非法字符(仅允许英文+数字)(illegal character in kf_account)"), + + /** + * 客服帐号个数超过限制(10个客服账号)(kf_account count exceeded) + */ + ACCOUNT_TOO_MANY(61456, "客服帐号个数超过限制(10个客服账号)(kf_account count exceeded)"), + + /** + * 无效头像文件类型(invalid file type) + */ + INVALID_FILE_TYPE(61457, "无效头像文件类型(invalid file type)"), + + /** + * 系统错误(system error) + */ + SYSTEM_ERROR(61450, "系统错误(system error)"), + + /** + * 日期格式错误 + */ + DATE_FORMAT_ERROR(61500, "日期格式错误"), + + /** + * 日期范围错误 + */ + DATE_RANGE_ERROR(61501, "日期范围错误"), + + /** + * 日期范围错误 + */ + DATA_MUST_UTF8(65318, "must use utf-8 charset hint: [ztWf508951894]"), + + /** + * POST数据参数不合法 + */ + ILLEGAL_POST_PARAM(9001001, "POST数据参数不合法"), + + /** + * 远端服务不可用 + */ + REMOTE_SERVER_ERROR(9001002, "远端服务不可用"), + + /** + * Ticket不合法 + */ + ILLEGAL_TICKET(9001003, "Ticket不合法"), + + /** + * 其他错误 + */ + OTHER_ERROR(99999, "其他错误"); + + /** + * 结果码 + */ + Integer code; + + /** + * 结果描述 + */ + String description; + + /** + * 返回结果枚举构造方法 + * + * @param code 结果码 + * @param description 结果描述 + */ + ResultType(Integer code, String description) { + this.code = code; + this.description = description; + } + + /** + * 通过code得到返回结果对象 + * + * @param code 结果码 + * @return 结果枚举对象 + */ + public static ResultType get(String code) { + BeanUtil.requireNonNull(code, "code is null"); + ResultType[] list = values(); + for (ResultType resultType : list) { + if (code.equals(resultType.getCode().toString())) { + return resultType; + } + } + return null; + } + + /** + * 获得结果码 + * + * @return 结果码 + */ + public Integer getCode() { + return code; + } + + /** + * 获得结果描述 + * + * @return 结果描述 + */ + public String getDescription() { + return description; + } + + @Override + public String toString() { + return "ResultType{" + + "code=" + code + + ", description='" + description + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/exception/WeixinException.java b/quick-wx-public/src/main/java/com/wx/pn/api/exception/WeixinException.java new file mode 100644 index 00000000..ba8cd5ba --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/exception/WeixinException.java @@ -0,0 +1,20 @@ +package com.wx.pn.api.exception; + +public class WeixinException extends RuntimeException { + + public WeixinException() { + super(); + } + + public WeixinException(String message) { + super(message); + } + + public WeixinException(String message, Throwable cause) { + super(message, cause); + } + + public WeixinException(Throwable cause) { + super(cause); + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/handler/ApiConfigChangeHandle.java b/quick-wx-public/src/main/java/com/wx/pn/api/handler/ApiConfigChangeHandle.java new file mode 100644 index 00000000..231e67d0 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/handler/ApiConfigChangeHandle.java @@ -0,0 +1,7 @@ +package com.wx.pn.api.handler; + +import java.util.Observer; + +public interface ApiConfigChangeHandle extends Observer { + +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/handler/GetTokenResponse.java b/quick-wx-public/src/main/java/com/wx/pn/api/handler/GetTokenResponse.java new file mode 100644 index 00000000..e8c00d88 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/handler/GetTokenResponse.java @@ -0,0 +1,28 @@ +package com.wx.pn.api.handler; + +import com.alibaba.fastjson.annotation.JSONField; +import com.wx.pn.api.model.BaseResponse; + +public class GetTokenResponse extends BaseResponse { + + @JSONField(name = "access_token") + private String accessToken; + @JSONField(name = "expires_in") + private Integer expiresIn; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Integer expiresIn) { + this.expiresIn = expiresIn; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/AccessToken.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/AccessToken.java new file mode 100644 index 00000000..c3e23eba --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/AccessToken.java @@ -0,0 +1,22 @@ +package com.wx.pn.api.model; + +public class AccessToken { + private String token; + private Integer expiresIn; + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Integer expiresIn) { + this.expiresIn = expiresIn; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/BaseMessage.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/BaseMessage.java new file mode 100644 index 00000000..ac39d694 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/BaseMessage.java @@ -0,0 +1,51 @@ +package com.wx.pn.api.model; + +public class BaseMessage { + //接收方账号(收到的OpenID) + private String ToUserName; + //开发者微信号 + private String FromUserName; + //消息创建时间(整型) + private long CreateTime; + //消息类型(text/music/news) + private String MsgType; + //星标刚收到的消息 + private int FuncFlag; + + public String getToUserName() { + return ToUserName; + } + public void setToUserName(String toUserName) { + ToUserName = toUserName; + } + public String getFromUserName() { + return FromUserName; + } + public void setFromUserName(String fromUserName) { + FromUserName = fromUserName; + } + public long getCreateTime() { + return CreateTime; + } + public void setCreateTime(long createTime) { + CreateTime = createTime; + } + public String getMsgType() { + return MsgType; + } + public void setMsgType(String msgType) { + MsgType = msgType; + } + public int getFuncFlag() { + return FuncFlag; + } + public void setFuncFlag(int funcFlag) { + FuncFlag = funcFlag; + } + + @Override + public String toString() { + return "BaseMessage{" + "ToUserName='" + ToUserName + '\'' + ", FromUserName='" + FromUserName + '\'' + + ", CreateTime=" + CreateTime + ", MsgType='" + MsgType + '\'' + ", FuncFlag=" + FuncFlag + '}'; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/BaseModel.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/BaseModel.java new file mode 100644 index 00000000..8a7c119f --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/BaseModel.java @@ -0,0 +1,22 @@ +package com.wx.pn.api.model; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; + +public abstract class BaseModel implements Model { + + public static final SerializerFeature[] DEFAULT_FORMAT = {SerializerFeature.WriteDateUseDateFormat, SerializerFeature.WriteEnumUsingToString, + SerializerFeature.WriteNonStringKeyAsString, SerializerFeature.QuoteFieldNames, SerializerFeature.SkipTransientField, + SerializerFeature.SortField, SerializerFeature.PrettyFormat}; + + /** + * 注意DEFAULT_FORMAT + * 对于枚举类 + * VIEW 会 转化为 view + * @return + */ + @Override + public String toJsonString() { + return JSON.toJSONString(this,DEFAULT_FORMAT); + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/BaseResponse.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/BaseResponse.java new file mode 100644 index 00000000..d8869f10 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/BaseResponse.java @@ -0,0 +1,35 @@ +package com.wx.pn.api.model; + +import com.wx.pn.api.enums.ResultType; +import com.wx.pn.api.utils.BeanUtil; +import org.apache.commons.lang3.StringUtils; + +public class BaseResponse extends BaseModel { + + private String errcode; + private String errmsg; + + public String getErrcode() { + return errcode; + } + + public void setErrcode(String errcode) { + this.errcode = errcode; + } + + public String getErrmsg() { + String result = this.errmsg; + //将接口返回的错误信息转换成中文,方便提示用户出错原因 + if (StringUtils.isNotBlank(this.errcode) && !ResultType.SUCCESS.getCode().toString().equals(this.errcode)) { + ResultType resultType = ResultType.get(this.errcode); + if (BeanUtil.nonNull(resultType)) { + result = resultType.getDescription(); + } + } + return result; + } + + public void setErrmsg(String errmsg) { + this.errmsg = errmsg; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/EventType.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/EventType.java new file mode 100644 index 00000000..1f1b72e4 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/EventType.java @@ -0,0 +1,23 @@ +package com.wx.pn.api.model; + +public final class EventType { + + public static final String SUBSCRIBE = "subscribe"; + public static final String UNSUBSCRIBE = "unsubscribe"; + public static final String CLICK = "CLICK"; + public static final String VIEW = "VIEW"; + public static final String LOCATION = "LOCATION"; + public static final String SCAN = "SCAN"; + public static final String SCANCODEPUSH = "scancode_push"; + public static final String SCANCODEWAITMSG = "scancode_waitmsg"; + public static final String PICSYSPHOTO = "pic_sysphoto"; + public static final String PICPHOTOORALBUM = "pic_photo_or_album"; + public static final String PICWEIXIN = "pic_weixin"; + public static final String LOCATIONSELECT = "location_select"; + public static final String TEMPLATESENDJOBFINISH = "TEMPLATESENDJOBFINISH"; + public static final String MASSSENDJOBFINISH="MASSSENDJOBFINISH"; + + private EventType() { + } + +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/Industry.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/Industry.java new file mode 100644 index 00000000..bb0632c8 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/Industry.java @@ -0,0 +1,32 @@ +package com.wx.pn.api.model; + +import com.alibaba.fastjson.annotation.JSONField; + +public class Industry extends BaseModel { + /** + * 行业1 + */ + @JSONField(name = "industry_id1") + private String industryId1; + /** + * 行业2 + */ + @JSONField(name = "industry_id2") + private String industryId2; + + public String getIndustryId1() { + return industryId1; + } + + public void setIndustryId1(String industryId1) { + this.industryId1 = industryId1; + } + + public String getIndustryId2() { + return industryId2; + } + + public void setIndustryId2(String industryId2) { + this.industryId2 = industryId2; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/Matchrule.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/Matchrule.java new file mode 100644 index 00000000..4e8f5d8a --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/Matchrule.java @@ -0,0 +1,58 @@ +package com.wx.pn.api.model; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * @author vector + * @date: 2018/11/8 0008 17:29 + */ +public class Matchrule extends BaseModel { + @JSONField(name = "tag_id") + private String tagId; + + @Deprecated + @JSONField(name = "group_id") + private String groupId; + + private String sex; + + private String country; + + private String province; + + private String city; + + @JSONField(name = "client_platform_type") + private String clientPlatformType; + + /** + * @return 群组ID + * @deprecated 微信官方不再建议使用群组, 用标签代替 + */ + @Deprecated + public String getGroupId() { + return groupId; + } + + /** + * @param groupId 群组ID + * @deprecated 微信官方不再建议使用群组, 用标签代替 + */ + @Deprecated + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + @Override + public String toString() { + return "Matchrule{" + + "tagId='" + tagId + '\'' + + ", groupId='" + groupId + '\'' + + ", sex='" + sex + '\'' + + ", country='" + country + '\'' + + ", province='" + province + '\'' + + ", city='" + city + '\'' + + ", clientPlatformType='" + clientPlatformType + '\'' + + '}'; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/Menu.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/Menu.java new file mode 100644 index 00000000..e2649697 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/Menu.java @@ -0,0 +1,47 @@ +package com.wx.pn.api.model; + +import com.alibaba.fastjson.annotation.JSONField; +import com.wx.pn.api.exception.WeixinException; + +import java.util.List; + +/** + * @author vector + * @date: 2018/11/8 0008 17:12 + */ +public class Menu extends BaseModel { + private List button; + + private Matchrule matchrule; + + @JSONField(name = "menuid") + private String menuId; + + public List getButton() { + return button; + } + + public void setButton(List button) { + if (null == button || button.size() > 3) { + throw new WeixinException("主菜单最多3个"); + } + this.button = button; + } + + public Matchrule getMatchrule() { + return matchrule; + } + + public void setMatchrule(Matchrule matchrule) { + this.matchrule = matchrule; + } + + public String getMenuId() { + return menuId; + } + + public void setMenuId(String menuId) { + this.menuId = menuId; + } + +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/MenuButton.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/MenuButton.java new file mode 100644 index 00000000..405ef5ff --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/MenuButton.java @@ -0,0 +1,81 @@ +package com.wx.pn.api.model; + +import com.alibaba.fastjson.annotation.JSONField; +import com.wx.pn.api.enums.MenuType; +import com.wx.pn.api.exception.WeixinException; + +import java.util.List; + +/** + * @author vector + * @date: 2018/11/8 0008 17:13 + */ +public class MenuButton extends BaseModel { + private MenuType type; + private String name; + private String key; + private String url; + + /** + * 菜单显示的永久素材的MaterialID,当MenuType值为media_id和view_limited时必需 + */ + @JSONField(name = "media_id") + private String mediaId; + + /** + * 二级菜单列表,每个一级菜单下最多5个 + */ + @JSONField(name = "sub_button") + private List subButton; + + public MenuType getType() { + return type; + } + + public void setType(MenuType type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMediaId() { + return mediaId; + } + + public void setMediaId(String mediaId) { + this.mediaId = mediaId; + } + + public List getSubButton() { + return subButton; + } + + public void setSubButton(List subButton) { + if (null == subButton || subButton.size() > 5) { + throw new WeixinException("子菜单最多只有5个"); + } + this.subButton = subButton; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/Model.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/Model.java new file mode 100644 index 00000000..749e000f --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/Model.java @@ -0,0 +1,13 @@ +package com.wx.pn.api.model; + +import java.io.Serializable; + +public interface Model extends Serializable { + + /** + * 将model转成json字符串 + * + * @return json字符串 + */ + String toJsonString(); +} \ No newline at end of file diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/ReqType.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/ReqType.java new file mode 100644 index 00000000..6a8088ce --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/ReqType.java @@ -0,0 +1,20 @@ +package com.wx.pn.api.model; + +public final class ReqType { + + public static final String TEXT = "text"; + public static final String IMAGE = "image"; + public static final String LINK = "link"; + public static final String LOCATION = "location"; + public static final String VOICE = "voice"; + public static final String VIDEO = "video"; + public static final String SHORT_VIDEO = "shortvideo"; + public static final String EVENT = "event"; + public static final String SUBSCRIBE = "subscribe"; + public static final String UNSUBSCRIBE = "unsubscribe"; + + + private ReqType() { + } + +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/TemplateMsg.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/TemplateMsg.java new file mode 100644 index 00000000..7c92dac3 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/TemplateMsg.java @@ -0,0 +1,58 @@ +package com.wx.pn.api.model; + +import com.alibaba.fastjson.annotation.JSONField; + +import java.util.Map; + +public class TemplateMsg extends BaseModel { + private String touser; + @JSONField(name = "template_id") + private String templateId; + private String url; + @Deprecated + private String topcolor; + + private Map data; + + public String getTouser() { + return touser; + } + + public void setTouser(String touser) { + this.touser = touser; + } + + public String getTemplateId() { + return templateId; + } + + public void setTemplateId(String templateId) { + this.templateId = templateId; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Deprecated + public String getTopcolor() { + return topcolor; + } + + @Deprecated + public void setTopcolor(String topcolor) { + this.topcolor = topcolor; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/TemplateParam.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/TemplateParam.java new file mode 100644 index 00000000..62069a8d --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/TemplateParam.java @@ -0,0 +1,46 @@ +package com.wx.pn.api.model; + +public class TemplateParam extends BaseModel { + + /** + * 值 + */ + private String value; + /** + * 颜色 + */ + private String color; + + + + public TemplateParam() { + super(); + } + + public TemplateParam(String value) { + super(); + this.value = value; + } + + public TemplateParam(String value, String color) { + super(); + this.value = value; + this.color = color; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/TextMessage.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/TextMessage.java new file mode 100644 index 00000000..a83072c0 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/TextMessage.java @@ -0,0 +1,14 @@ +package com.wx.pn.api.model; + +public class TextMessage extends BaseMessage { + private String Content; + + public String getContent() { + return Content; + } + + public void setContent(String content) { + Content = content; + } + +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/Voice.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/Voice.java new file mode 100644 index 00000000..6ed4693c --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/Voice.java @@ -0,0 +1,24 @@ +package com.wx.pn.api.model; + +/** + * @author vector + * @date: 2018/11/5 0005 14:19 + */ +public class Voice { + private String MediaId; + + public Voice() { + } + + public Voice(String mediaId) { + this.MediaId = mediaId; + } + + public String getMediaId() { + return MediaId; + } + + public void setMediaId(String mediaId) { + MediaId = mediaId; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/model/VoiceMessage.java b/quick-wx-public/src/main/java/com/wx/pn/api/model/VoiceMessage.java new file mode 100644 index 00000000..32f62926 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/model/VoiceMessage.java @@ -0,0 +1,13 @@ +package com.wx.pn.api.model; + +public class VoiceMessage extends BaseMessage { + private Voice Voice; + + public Voice getVoice() { + return Voice; + } + + public void setVoice(Voice voice) { + Voice = voice; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/response/AddTemplateResponse.java b/quick-wx-public/src/main/java/com/wx/pn/api/response/AddTemplateResponse.java new file mode 100644 index 00000000..a13ebeab --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/response/AddTemplateResponse.java @@ -0,0 +1,20 @@ +package com.wx.pn.api.response; + +import com.alibaba.fastjson.annotation.JSONField; +import com.wx.pn.api.model.BaseResponse; + +public class AddTemplateResponse extends BaseResponse { + /** + * 模版id + */ + @JSONField(name = "template_id") + private String templateId; + + public String getTemplateId() { + return templateId; + } + + public void setTemplateId(String templateId) { + this.templateId = templateId; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/response/DownloadMediaResponse.java b/quick-wx-public/src/main/java/com/wx/pn/api/response/DownloadMediaResponse.java new file mode 100644 index 00000000..7164173a --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/response/DownloadMediaResponse.java @@ -0,0 +1,55 @@ +package com.wx.pn.api.response; + +import com.wx.pn.api.model.BaseResponse; +import com.wx.pn.api.utils.StreamUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class DownloadMediaResponse extends BaseResponse { + + private static final Logger LOG = LoggerFactory.getLogger(DownloadMediaResponse.class); + private String fileName; + private byte[] content; + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public void setContent(InputStream content, Integer length) { + ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + try { + StreamUtil.copy(content, byteOutputStream); + byte[] temp = byteOutputStream.toByteArray(); + if (temp.length > length) { + this.content = new byte[length]; + System.arraycopy(temp, 0, this.content, 0, length); + } else { + this.content = temp; + } + } catch (IOException e) { + LOG.error("异常", e); + } + } + + /** + * 如果成功,则可以靠这个方法将数据输出 + * + * @param out 调用者给的输出流 + * @throws IOException 写流出现异常 + */ + public void writeTo(OutputStream out) throws IOException { + out.write(this.content); + out.flush(); + out.close(); + } + +} \ No newline at end of file diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/response/GetJsApiTicketResponse.java b/quick-wx-public/src/main/java/com/wx/pn/api/response/GetJsApiTicketResponse.java new file mode 100644 index 00000000..ed331c26 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/response/GetJsApiTicketResponse.java @@ -0,0 +1,28 @@ +package com.wx.pn.api.response; + +import com.alibaba.fastjson.annotation.JSONField; +import com.wx.pn.api.model.BaseResponse; + +public class GetJsApiTicketResponse extends BaseResponse { + + private String ticket; + + @JSONField(name = "expires_in") + private Integer expiresIn; + + public String getTicket() { + return ticket; + } + + public void setTicket(String ticket) { + this.ticket = ticket; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Integer expiresIn) { + this.expiresIn = expiresIn; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/response/SendTemplateResponse.java b/quick-wx-public/src/main/java/com/wx/pn/api/response/SendTemplateResponse.java new file mode 100644 index 00000000..3fa144db --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/response/SendTemplateResponse.java @@ -0,0 +1,21 @@ +package com.wx.pn.api.response; + +import com.alibaba.fastjson.annotation.JSONField; +import com.wx.pn.api.model.BaseResponse; + +public class SendTemplateResponse extends BaseResponse { + + /** + * 消息id + */ + @JSONField(name = "msgid") + private String msgid; + + public String getMsgid() { + return msgid; + } + + public void setMsgid(String msgid) { + this.msgid = msgid; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/response/UploadMediaResponse.java b/quick-wx-public/src/main/java/com/wx/pn/api/response/UploadMediaResponse.java new file mode 100644 index 00000000..99de689d --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/response/UploadMediaResponse.java @@ -0,0 +1,39 @@ +package com.wx.pn.api.response; + +import com.alibaba.fastjson.annotation.JSONField; +import com.wx.pn.api.model.BaseResponse; + +import java.util.Date; + +public class UploadMediaResponse extends BaseResponse { + + private String type; + @JSONField(name = "media_id") + private String mediaId; + @JSONField(name = "created_at") + private Date createdAt; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getMediaId() { + return mediaId; + } + + public void setMediaId(String mediaId) { + this.mediaId = mediaId; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } +} \ No newline at end of file diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/servlet/WeixinSupport.java b/quick-wx-public/src/main/java/com/wx/pn/api/servlet/WeixinSupport.java new file mode 100644 index 00000000..cd28ff89 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/servlet/WeixinSupport.java @@ -0,0 +1,9 @@ +package com.wx.pn.api.servlet; + +/** + * @author vector + * @date: 2018/11/9 0009 11:37 + */ +public abstract class WeixinSupport { + protected abstract String getToken(); +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/utils/BeanUtil.java b/quick-wx-public/src/main/java/com/wx/pn/api/utils/BeanUtil.java new file mode 100644 index 00000000..d9c71fc5 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/utils/BeanUtil.java @@ -0,0 +1,44 @@ +package com.wx.pn.api.utils; + +public final class BeanUtil { + + /** + * 此类不需要实例化 + */ + private BeanUtil() { + } + + /** + * 判断对象是否为null + * + * @param object 需要判断的对象 + * @return 是否为null + */ + public static boolean isNull(Object object) { + return null == object; + } + + /** + * 判断对象是否不为null + * + * @param object 需要判断的对象 + * @return 是否不为null + */ + public static boolean nonNull(Object object) { + return null != object; + } + + /** + * 判断对象是否为空,如果为空,直接抛出异常 + * + * @param object 需要检查的对象 + * @param errorMessage 异常信息 + * @return 非空的对象 + */ + public static Object requireNonNull(Object object, String errorMessage) { + if (null == object) { + throw new NullPointerException(errorMessage); + } + return object; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/utils/MessageUtil.java b/quick-wx-public/src/main/java/com/wx/pn/api/utils/MessageUtil.java new file mode 100644 index 00000000..42b6e487 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/utils/MessageUtil.java @@ -0,0 +1,133 @@ +package com.wx.pn.api.utils; + + +import com.thoughtworks.xstream.XStream; +import com.wx.pn.api.model.*; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MessageUtil { + + private static Logger logger = LoggerFactory.getLogger(MessageUtil.class); + + private final static Pattern p = Pattern.compile("/::\\)|/::~|/::B|/::\\||/:8-\\)|/::<|/::$|/::X|/::Z|/::'\\(|/::-\\||/::@|/::P|/::D|/::O|/::\\(|/::\\+|/:--b|/::Q|/::T|/:,@P|/:,@-D|/::d|/:,@o|/::g|/:\\|-\\)|/::!|/::L|/::>|/::,@|/:,@f|/::-S|/:\\?|/:,@x|/:,@@|/::8|/:,@!|/:!!!|/:xx|/:bye|/:wipe|/:dig|/:handclap|/:&-\\(|/:B-\\)|/:<@|/:@>|/::-O|/:>-\\||/:P-\\(|/::'\\||/:X-\\)|/::\\*|/:@x|/:8\\*|/:pd|/:|/:beer|/:basketb|/:oo|/:coffee|/:eat|/:pig|/:rose|/:fade|/:showlove|/:heart|/:break|/:cake|/:li|/:bome|/:kn|/:footb|/:ladybug|/:shit|/:moon|/:sun|/:gift|/:hug|/:strong|/:weak|/:share|/:v|/:@\\)|/:jj|/:@@|/:bad|/:lvu|/:no|/:ok|/:love|/:|/:jump|/:shake|/:|/:circle|/:kotow|/:turn|/:skip|/:oY|/:#-0|/:hiphot|/:kiss|/:<&|/:&>"); + +// private static ApiConfig apiConfig = new ApiConfig(); + + public static Map xmlToMap(HttpServletRequest request) { + Map map = new HashMap<>(); + SAXReader reader = new SAXReader(); + InputStream inputStream = null; + try { + inputStream = request.getInputStream(); + Document document = reader.read(inputStream); + Element root = document.getRootElement(); + List elementList = root.elements(); + for (Element element : elementList) { + map.put(element.getName(), element.getText()); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return map; + } + + /** + * 将发送消息封装成对应的xml格式 + */ + public static String messageToxml(BaseMessage message) { + XStream xstream = new XStream(); + xstream.alias("xml", message.getClass()); + return xstream.toXML(message); + } + + /** + * 封装发送消息对象,封装时,需要将调换发送者和接收者的关系 + * + * @param FromUserName + * @param ToUserName + */ + public static String subscribeMessage(String FromUserName, String ToUserName) { + String message = "Hi!我的小伙伴~欢迎关注人事情报科!\n" + + "\n" + + "快速搜索查看2000万人才简历,点击【搜索】\n" + + "\n" + + "与HR和猎头加好友交换简历~~点击 【交换简历】\n" + + "\n" + + "体验产品,领新手红包,点击【下载App】\n" + + "\n" + + "更多人事资讯请时刻关注我们哦,定期免费课程大放送~"; + return reversalMessage(FromUserName, ToUserName, message); + } + + /** + * 封装发送消息对象,封装时,需要将调换发送者和接收者的关系 + * + * @param fromUserName + * @param toUserName + * @param Content + */ + public static String reversalMessage(String fromUserName, String toUserName, String Content) { + TextMessage text = new TextMessage(); + text.setToUserName(fromUserName); + text.setFromUserName(toUserName); + text.setContent(Content); + text.setCreateTime(new Date().getTime()); + text.setMsgType(ReqType.TEXT); + return messageToxml(text); + } + + /** + * 封装voice消息对象 + * + * @param fromUserName + * @param toUserName + * @param mediaId + * @return + */ + public static String reversalVoiceMessage(String fromUserName, String toUserName, String mediaId) { + VoiceMessage voiceMessage = new VoiceMessage(); + voiceMessage.setToUserName(fromUserName); + voiceMessage.setFromUserName(toUserName); + voiceMessage.setVoice(new Voice(mediaId)); + voiceMessage.setCreateTime(new Date().getTime()); + voiceMessage.setMsgType(ReqType.VOICE); + return messageToxml(voiceMessage); + } + + /** + * 判断是否是QQ表情 + * + * @param content + * @return + */ + public static boolean isQqFace(String content) { + boolean result = false; + // 判断QQ表情的正则表达式 + Matcher m = p.matcher(content); + if (m.matches()) { + result = true; + } + return result; + } + +} \ No newline at end of file diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/utils/NetWorkCenter.java b/quick-wx-public/src/main/java/com/wx/pn/api/utils/NetWorkCenter.java new file mode 100644 index 00000000..755ef55c --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/utils/NetWorkCenter.java @@ -0,0 +1,357 @@ +package com.wx.pn.api.utils; + +import com.alibaba.fastjson.JSON; +import com.wx.pn.api.model.BaseResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.utils.HttpClientUtils; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.entity.mime.content.StringBody; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; + +public final class NetWorkCenter { + + /** + * 默认连接超时时间(毫秒) + * 由于目前的设计原因,该变量定义为静态的,超时时间不能针对每一次的请求做定制 + * 备选优化方案: + * 1.考虑是否重新设计这个工具类,每次请求都需要创建一个实例; + * 2.请求方法里加入超时时间参数 + * 或者说是否没必要定制,10秒是一个比较适中的选择,但有些请求可能就是需要快速给出结果T_T + */ + public static final int CONNECT_TIMEOUT = 10 * 1000; + + /** + * 日志输出组件 + */ + private static final Logger LOG = LoggerFactory.getLogger(NetWorkCenter.class); + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + /** + * 私有化构造器 + * 不允许外界创建实例 + */ + private NetWorkCenter() { + LOG.warn("Oh,my god!!!How do you call this method?!"); + LOG.warn("You shouldn't create me!!!"); + LOG.warn("Look my doc again!!!"); + } + + /** + * 发起HTTP POST同步请求 + * jdk8使用函数式方式处理请求结果 + * jdk6使用内部类方式处理请求结果 + * + * @param url 请求对应的URL地址 + * @param paramData 请求所带参数,目前支持JSON格式的参数 + * @param callback 请求收到响应后回调函数,参数有2个,第一个为resultCode,即响应码,比如200为成功,404为不存在,500为服务器发生错误; + * 第二个为resultJson,即响应回来的数据报文 + */ + public static void post(String url, String paramData, ResponseCallback callback) { + post(url, paramData, null, callback); + } + + public static BaseResponse post(String url, String paramData) { + final BaseResponse[] response = new BaseResponse[]{null}; + post(url, paramData, new ResponseCallback() { + @Override + public void onResponse(int resultCode, String resultJson) { + if (200 == resultCode) { + BaseResponse r = JSON.parseObject(resultJson, BaseResponse.class); + r.setErrmsg(resultJson); + response[0] = r; + } else {//请求本身就失败了 + response[0] = new BaseResponse(); + response[0].setErrcode(String.valueOf(resultCode)); + response[0].setErrmsg("请求失败"); + } + } + }); + return response[0]; + } + + /** + * 发起HTTP POST同步请求 + * jdk8使用函数式方式处理请求结果 + * jdk6使用内部类方式处理请求结果 + * + * @param url 请求对应的URL地址 + * @param paramData 请求所带参数,目前支持JSON格式的参数 + * @param fileList 需要一起发送的文件列表 + * @param callback 请求收到响应后回调函数,参数有2个,第一个为resultCode,即响应码,比如200为成功,404为不存在,500为服务器发生错误; + * 第二个为resultJson,即响应回来的数据报文 + */ + public static void post(String url, String paramData, List fileList, ResponseCallback callback) { + doRequest(RequestMethod.POST, url, paramData, fileList, callback); + } + + public static BaseResponse post(String url, String paramData, List fileList) { + final BaseResponse[] response = new BaseResponse[]{null}; + post(url, paramData, fileList, new ResponseCallback() { + @Override + public void onResponse(int resultCode, String resultJson) { + if (200 == resultCode) { + BaseResponse r = JSON.parseObject(resultJson, BaseResponse.class); + if (StringUtils.isBlank(r.getErrcode())) { + r.setErrcode("0"); + } + r.setErrmsg(resultJson); + response[0] = r; + } else {//请求本身就失败了 + response[0] = new BaseResponse(); + response[0].setErrcode(String.valueOf(resultCode)); + response[0].setErrmsg("请求失败"); + } + } + }); + return response[0]; + } + + /** + * 发起HTTP GET同步请求 + * jdk8使用函数式方式处理请求结果 + * jdk6使用内部类方式处理请求结果 + * + * @param url 请求对应的URL地址 + * @param paramMap GET请求所带参数Map,即URL地址问号后面所带的键值对,很蛋疼的实现方式,后续得改进,还没什么好的方案 + * @param callback 请求收到响应后回调函数,参数有2个,第一个为resultCode,即响应码,比如200为成功,404为不存在,500为服务器发生错误; + * 第二个为resultJson,即响应回来的数据报文 + */ + public static void get(String url, Map paramMap, ResponseCallback callback) { + String paramData = null; + if (null != paramMap && !paramMap.isEmpty()) { + StringBuilder buffer = new StringBuilder(); + //根据传进来的参数拼url后缀- -! + for (Map.Entry param : paramMap.entrySet()) { + buffer.append(param.getKey()).append("=").append(param.getValue()).append("&"); + } + //去掉最后一个&符号 + paramData = buffer.substring(0, buffer.length() - 1); + } + doRequest(RequestMethod.GET, url, paramData, null, callback); + } + + public static BaseResponse get(String url) { + final BaseResponse[] response = new BaseResponse[]{null}; + doRequest(RequestMethod.GET, url, null, null, new ResponseCallback() { + @Override + public void onResponse(int resultCode, String resultJson) { + if (200 == resultCode) { + BaseResponse r = JSON.parseObject(resultJson, BaseResponse.class); + if (StringUtils.isBlank(r.getErrcode())) { + r.setErrcode("0"); + } + r.setErrmsg(resultJson); + response[0] = r; + } else {//请求本身就失败了 + response[0] = new BaseResponse(); + response[0].setErrcode(String.valueOf(resultCode)); + response[0].setErrmsg("请求失败"); + } + } + }); + return response[0]; + } + + /** + * 处理HTTP请求 + * 基于org.apache.http.client包做了简单的二次封装 + * + * @param method HTTP请求类型 + * @param url 请求对应的URL地址 + * @param paramData 请求所带参数,目前支持JSON格式的参数 + * @param fileList 需要一起发送的文件列表 + * @param callback 请求收到响应后回调函数,参数有2个,第一个为resultCode,即响应码,比如200为成功,404为不存在,500为服务器发生错误; + * 第二个为resultJson,即响应回来的数据报文 + */ + private static void doRequest(final RequestMethod method, final String url, final String paramData, + final List fileList, final ResponseCallback callback) { + //如果url没有传入,则直接返回 + if (null == url || url.isEmpty()) { + LOG.warn("The url is null or empty!!You must give it to me!OK?"); + return; + } + //默认期望调用者传入callback函数 + boolean haveCallback = true; + /* + * 支持不传回调函数,只输出一个警告,并改变haveCallback标识 + * 用于一些不需要后续处理的请求,比如只是发送一个心跳包等等 + */ + if (null == callback) { + LOG.warn("--------------no callback block!--------------"); + haveCallback = false; + } + LOG.debug("-----------------请求地址:{}-----------------", url); + //配置请求参数 + RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(CONNECT_TIMEOUT) + .setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(CONNECT_TIMEOUT).build(); + CloseableHttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(config).build(); + HttpUriRequest request = null; + switch (method) { + case GET: + String getUrl = url; + if (null != paramData) { + getUrl += "?" + paramData; + } + request = new HttpGet(getUrl); + break; + case POST: + LOG.debug("请求入参:"); + LOG.debug(paramData); + request = new HttpPost(url); + //上传文件 + if (null != fileList && !fileList.isEmpty()) { + LOG.debug("上传文件..."); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + for (File file : fileList) { + //只能上传文件哦 ^_^ + if (file.isFile()) { + FileBody fb = new FileBody(file); + builder.addPart("media", fb); + } else {//如果上传内容有不是文件的,则不发起本次请求 + LOG.warn("The target '{}' not a file,please check and try again!", file.getPath()); + return; + } + } + if (null != paramData) { + builder.addPart("description", new StringBody(paramData, ContentType.APPLICATION_JSON)); + } + ((HttpPost) request).setEntity(builder.build()); + } else {//不上传文件的普通请求 + if (null != paramData) { + // 目前支持JSON格式的数据 + StringEntity jsonEntity = new StringEntity(paramData, ContentType.APPLICATION_JSON); + ((HttpPost) request).setEntity(jsonEntity); + } + } + break; + case PUT: + case DELETE: + default: + LOG.warn("-----------------请求类型:{} 暂不支持-----------------", method.toString()); + break; + } + CloseableHttpResponse response = null; + try { + long start = System.currentTimeMillis(); + //发起请求 + response = client.execute(request); + long time = System.currentTimeMillis() - start; + LOG.debug("本次请求'{}'耗时:{}ms", url.substring(url.lastIndexOf("/") + 1, url.length()), time); + int resultCode = response.getStatusLine().getStatusCode(); + HttpEntity entity = response.getEntity(); + //此流不是操作系统资源,不用关闭,ByteArrayOutputStream源码里close也是个空方法-0_0- + // OutputStream os = new ByteArrayOutputStream(); + // entity.writeTo(os); + // String resultJson = os.toString(); + String resultJson = EntityUtils.toString(entity, UTF_8); + //返回码200,请求成功;其他情况都为请求出现错误 + if (HttpStatus.SC_OK == resultCode) { + LOG.debug("-----------------请求成功-----------------"); + LOG.debug("响应结果:"); + LOG.debug(resultJson); + if (haveCallback) { + callback.onResponse(resultCode, resultJson); + } + } else { + if (haveCallback) { + LOG.warn("-----------------请求出现错误,错误码:{}-----------------", resultCode); + callback.onResponse(resultCode, resultJson); + } + } + } catch (ClientProtocolException e) { + LOG.error("ClientProtocolException:", e); + LOG.warn("-----------------请求出现异常:{}-----------------", e.toString()); + if (haveCallback) { + callback.onResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.toString()); + } + } catch (IOException e) { + LOG.error("IOException:", e); + LOG.warn("-----------------请求出现IO异常:{}-----------------", e.toString()); + if (haveCallback) { + callback.onResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.toString()); + } + } catch (Exception e) { + LOG.error("Exception:", e); + LOG.warn("-----------------请求出现其他异常:{}-----------------", e.toString()); + if (haveCallback) { + callback.onResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.toString()); + } + } finally { + //abort the request + if (null != request && !request.isAborted()) { + request.abort(); + } + //close the connection + HttpClientUtils.closeQuietly(client); + HttpClientUtils.closeQuietly(response); + } + } + + /** + * 标识HTTP请求类型枚举 + * + * @author peiyu + * @since 1.0 + */ + enum RequestMethod { + /** + * HTTP GET请求 + * 一般对应的是查询业务接口 + */ + GET, + /** + * HTTP POST请求 + * 一般对应的是新增业务接口 + * 只是一般都通用这个请求方式来处理一切接口了T_T + */ + POST, + /** + * HTTP PUT请求,用的太少,暂不支持 + * 一般对应的是更新业务接口 + */ + PUT, + /** + * HTTP DELETE请求,用的太少,暂不支持 + * 一般对应的是删除业务接口 + */ + DELETE + } + + /** + * 自定义HTTP响应回调接口,用于兼容jdk6 + * + * @author peiyu + * @since 1.1 + */ + // @FunctionalInterface + public interface ResponseCallback { + /** + * 响应后回调方法 + * + * @param resultCode 响应结果码,比如200成功,404不存在,500服务器异常等 + * @param resultJson 响应内容,目前支持JSON字符串 + */ + void onResponse(int resultCode, String resultJson); + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/utils/SignUtil.java b/quick-wx-public/src/main/java/com/wx/pn/api/utils/SignUtil.java new file mode 100644 index 00000000..ce647f56 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/utils/SignUtil.java @@ -0,0 +1,73 @@ +package com.wx.pn.api.utils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * @author vector + * @date: 2018/11/2 0002 18:24 + */ +public class SignUtil { + + /** + * 验证签名 + * + * @param signature + * @param timestamp + * @param nonce + * @return + */ + public static boolean checkSignature(String token, String signature, String timestamp, String nonce) { + String[] arr = new String[]{token, timestamp, nonce}; + // 将token、timestamp、nonce三个参数进行字典序排序 + Arrays.sort(arr); + StringBuilder content = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + content.append(arr[i]); + } + MessageDigest md = null; + String tmpStr = null; + + try { + md = MessageDigest.getInstance("SHA-1"); + // 将三个参数字符串拼接成一个字符串进行sha1加密 + byte[] digest = md.digest(content.toString().getBytes()); + tmpStr = byteToStr(digest); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + content = null; + // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信 + return tmpStr != null && tmpStr.equals(signature.toUpperCase()); + } + + /** + * 将字节数组转换为十六进制字符串 + * + * @param byteArray + * @return + */ + private static String byteToStr(byte[] byteArray) { + String strDigest = ""; + for (int i = 0; i < byteArray.length; i++) { + strDigest += byteToHexStr(byteArray[i]); + } + return strDigest; + } + + /** + * 将字节转换为十六进制字符串 + * + * @param mByte + * @return + */ + private static String byteToHexStr(byte mByte) { + char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + char[] tempArr = new char[2]; + tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; + tempArr[1] = Digit[mByte & 0X0F]; + return new String(tempArr); + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/utils/StreamUtil.java b/quick-wx-public/src/main/java/com/wx/pn/api/utils/StreamUtil.java new file mode 100644 index 00000000..1961b8cc --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/utils/StreamUtil.java @@ -0,0 +1,53 @@ +package com.wx.pn.api.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public final class StreamUtil { + + private static final Logger LOG = LoggerFactory.getLogger(StreamUtil.class); + + private StreamUtil() { + } + + /** + * 将输入流的内容复制到输出流里 + * + * @param in 输入流 + * @param out 输出流 + * @return 复制的数据字节数 + * @throws IOException IO异常 + */ + public static int copy(InputStream in, OutputStream out) throws IOException { + BeanUtil.requireNonNull(in, "No InputStream specified"); + BeanUtil.requireNonNull(out, "No OutputStream specified"); + int byteCount = 0; + byte[] buffer = new byte[4096]; + int bytesRead1; + for (; (bytesRead1 = in.read(buffer)) != -1; byteCount += bytesRead1) { + out.write(buffer, 0, bytesRead1); + } + out.flush(); + return byteCount; + } + + /** + * 关闭需要关闭的对象,如果关闭出错,给出警告 + * + * @param closeable 需要关闭的对象 + */ + public static void closeWithWarn(Closeable closeable) { + if (BeanUtil.nonNull(closeable)) { + try { + closeable.close(); + } catch (IOException e) { + LOG.warn("关闭流出错......", e); + } + } + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/utils/WXTip.java b/quick-wx-public/src/main/java/com/wx/pn/api/utils/WXTip.java new file mode 100644 index 00000000..d2e540d2 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/utils/WXTip.java @@ -0,0 +1,17 @@ +package com.wx.pn.api.utils; + + +/** + * @author vector + * @date: 2018/11/6 0006 11:28 + * 异常提示 + */ +public class WXTip { + public static String errTip(String fromUserName, String toUserName) { + return MessageUtil.reversalMessage(fromUserName, toUserName, "神马情况~"); + } + + public static String warnTip(String fromUserName, String toUserName) { + return MessageUtil.reversalMessage(fromUserName, toUserName, "警告信息▄█▀█●给跪了"); + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/api/utils/WXUtil.java b/quick-wx-public/src/main/java/com/wx/pn/api/utils/WXUtil.java new file mode 100644 index 00000000..b3eedbd5 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/api/utils/WXUtil.java @@ -0,0 +1,4 @@ +package com.wx.pn.api.utils; + +public class WXUtil { +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/config/CorsConfig.java b/quick-wx-public/src/main/java/com/wx/pn/config/CorsConfig.java new file mode 100644 index 00000000..67d0789a --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/config/CorsConfig.java @@ -0,0 +1,21 @@ +package com.wx.pn.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +public class CorsConfig extends WebMvcConfigurerAdapter { + + + @Override + public void addCorsMappings(CorsRegistry registry) { + + registry.addMapping("/**") + .allowedOrigins("*") + .allowCredentials(true) + .allowedMethods("GET", "POST", "DELETE", "PUT") + .maxAge(3600); + } + +} \ No newline at end of file diff --git a/quick-wx-public/src/main/java/com/wx/pn/config/GlobalExceptionHandler.java b/quick-wx-public/src/main/java/com/wx/pn/config/GlobalExceptionHandler.java new file mode 100644 index 00000000..c02aa3e2 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/config/GlobalExceptionHandler.java @@ -0,0 +1,43 @@ +package com.wx.pn.config; + +import com.wx.pn.utils.BaseResp; +import com.wx.pn.utils.LogExceptionUtil; +import com.wx.pn.utils.ResultStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; + +@ControllerAdvice(annotations = {Controller.class}) +public class GlobalExceptionHandler { + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * 系统异常处理,比如:404 + * 服务器异常自动捕捉为 500 + * + * @param req + * @param e + * @return + * @throws Exception + */ + @ExceptionHandler(value = Exception.class) + @ResponseBody + public BaseResp defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { + e.printStackTrace(); + logger.error("捕捉到异常:{}", LogExceptionUtil.LogExceptionStack(e)); + BaseResp baseResp = new BaseResp(); + baseResp.setMessage(LogExceptionUtil.LogExceptionStack(e)); + if (e instanceof org.springframework.web.servlet.NoHandlerFoundException) { + baseResp.setCode(ResultStatus.http_status_not_found.getErrorCode()); + } else { + baseResp.setCode(ResultStatus.http_status_internal_server_error.getErrorCode()); + } + return baseResp; + } +} \ No newline at end of file diff --git a/quick-wx-public/src/main/java/com/wx/pn/config/SessionCache.java b/quick-wx-public/src/main/java/com/wx/pn/config/SessionCache.java new file mode 100644 index 00000000..9ec95227 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/config/SessionCache.java @@ -0,0 +1,97 @@ +package com.wx.pn.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * @author vector + * @date: 2018/11/8 0008 11:02 + */ +@Component +public class SessionCache { + + private static final int EXPIRE_TIME = 30; // 30s + + // public static final String V1 = "V1"; + public static final String V2 = "V2"; + public static final String V3 = "V3"; + + private static final String VOICE_OPENID_V1_SESSION = "openid:voice:session:v1:"; + private static final String TEXT_OPENID_V1_SESSION = "openid:text:session:v1:"; + + private static final String VOICE_OPENID_V2_SESSION = "openid:voice:session:v2:"; + private static final String TEXT_OPENID_V2_SESSION = "openid:text:session:v2:"; + + private static final String VOICE_OPENID_V3_SESSION = "openid:voice:session:v3:"; + private static final String TEXT_OPENID_V3_SESSION = "openid:text:session:v3:"; + + public static final String DIALOG_VOICE_TYPE = "voice"; + public static final String DIALOG_TEXT_TYPE = "text"; + + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + /** + * + * @param version 版本号 + * @param openId 用户唯一识别,可位微信端的openid,也可以是通话过程的callid + * @param session 通过模块与交互模块的session,通话系统中,callid就是session + * @param type + */ + public void refreshSession(String version, String openId, String session, String type) { + set(generateKey(version, openId, type), session); + } + + public Boolean isExist(String version, String openId, String type) { + return stringRedisTemplate.hasKey(generateKey(version, openId, type)); + } + + public String getSession(String version, String openId, String type) { + return get(generateKey(version, openId, type)); + } + + public void delSession(String version, String openId, String type) { + del(version, openId, type); + } + + private String get(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + + private void set(String key, String value) { + stringRedisTemplate.opsForValue().set(key, value, EXPIRE_TIME, TimeUnit.SECONDS); + } + + private void del(String version, String openId, String type) { + stringRedisTemplate.delete(generateKey(version, openId, type)); + } + + private String generateKey(String version, String openId, String type) { +// if (V1.equals(version)) { +// if (DIALOG_VOICE_TYPE.equals(type)) { +// return VOICE_OPENID_V1_SESSION + openId; +// } else { +// return TEXT_OPENID_V1_SESSION + openId; +// } +// } else + if (V2.equals(version)) { + if (DIALOG_VOICE_TYPE.equals(type)) { + return VOICE_OPENID_V2_SESSION + openId; + } else { + return TEXT_OPENID_V2_SESSION + openId; + } + } else { + if (DIALOG_VOICE_TYPE.equals(type)) { + return VOICE_OPENID_V3_SESSION + openId; + } else { + return TEXT_OPENID_V3_SESSION + openId; + } + } + + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/config/WebLogAspect.java b/quick-wx-public/src/main/java/com/wx/pn/config/WebLogAspect.java new file mode 100644 index 00000000..470f6b52 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/config/WebLogAspect.java @@ -0,0 +1,42 @@ +package com.wx.pn.config; + + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; + +@Aspect +@Component +public class WebLogAspect { + private static Logger logger = LoggerFactory.getLogger(WebLogAspect.class); + + + @Pointcut("execution(public * com.base.voice..controller.*.*(..))")//两个..代表所有子目录,最后括号里的两个..代表所有参数 + public void logPointCut() { + } + + @Around("logPointCut()") + public Object doAround(ProceedingJoinPoint pjp) throws Throwable { + long startTime = System.currentTimeMillis(); + Object ob = pjp.proceed();// ob 为方法的返回值 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + logger.info("请求地址 : " + request.getRequestURL().toString()); + logger.info("HTTP METHOD : " + request.getMethod()); + logger.info("IP : " + request.getRemoteAddr()); + logger.info("CLASS_METHOD : " + pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName()); + logger.info("参数 : " + Arrays.toString(pjp.getArgs())); + logger.info("结果 : " + ob); + logger.info("耗时 : {} millisecond", (System.nanoTime() - startTime) / 1_000_000); + return ob; + } +} diff --git a/quick-wx-public/src/main/java/com/wx/pn/model/Test.java b/quick-wx-public/src/main/java/com/wx/pn/model/Test.java new file mode 100644 index 00000000..3729f496 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/model/Test.java @@ -0,0 +1,8 @@ +package com.wx.pn.model; + +/** + * @author vector + * @date: 2019/2/15 0015 11:43 + */ +public class Test { +} diff --git a/quick-spark/src/main/java/spark/util/BaseResp.java b/quick-wx-public/src/main/java/com/wx/pn/utils/BaseResp.java similarity index 58% rename from quick-spark/src/main/java/spark/util/BaseResp.java rename to quick-wx-public/src/main/java/com/wx/pn/utils/BaseResp.java index 531e6903..756e7b9b 100644 --- a/quick-spark/src/main/java/spark/util/BaseResp.java +++ b/quick-wx-public/src/main/java/com/wx/pn/utils/BaseResp.java @@ -1,4 +1,8 @@ -package spark.util; +package com.wx.pn.utils; + + +import org.apache.commons.lang3.time.DateFormatUtils; +import org.apache.commons.lang3.time.DateUtils; import java.util.Date; @@ -21,7 +25,7 @@ public class BaseResp { */ private T data; - private long currentTime; + private String currentTime; public int getCode() { return code; @@ -39,7 +43,7 @@ public void setMessage(String message) { this.message = message; } - public Object getData() { + public T getData() { return data; } @@ -47,34 +51,44 @@ public void setData(T data) { this.data = data; } - public long getCurrentTime() { + public String getCurrentTime() { return currentTime; } - public void setCurrentTime(long currentTime) { + public void setCurrentTime(String currentTime) { this.currentTime = currentTime; } - //提供几种构造方法 + public BaseResp(){ + this.currentTime = DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"); + } + public BaseResp(int code, String message, T data) { this.code = code; this.message = message; this.data = data; - this.currentTime = new Date().getTime(); + this.currentTime = DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"); + } + + public BaseResp(int code, ResultStatus resultStatus,String message) { + this.code = code; + this.message = message; + this.data = data; + this.currentTime = DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"); } public BaseResp(ResultStatus resultStatus) { this.code = resultStatus.getErrorCode(); this.message = resultStatus.getErrorMsg(); this.data = data; - this.currentTime = new Date().getTime(); + this.currentTime = DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"); } public BaseResp(ResultStatus resultStatus, T data) { this.code = resultStatus.getErrorCode(); this.message = resultStatus.getErrorMsg(); this.data = data; - this.currentTime = new Date().getTime(); + this.currentTime = DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"); } diff --git a/quick-wx-public/src/main/java/com/wx/pn/utils/LogExceptionUtil.java b/quick-wx-public/src/main/java/com/wx/pn/utils/LogExceptionUtil.java new file mode 100644 index 00000000..56282d16 --- /dev/null +++ b/quick-wx-public/src/main/java/com/wx/pn/utils/LogExceptionUtil.java @@ -0,0 +1,25 @@ +package com.wx.pn.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Created with IDEA + * User: vector + * Data: 2017/11/22 + * Time: 20:16 + * Description: + */ +public class LogExceptionUtil { + + /** + * 在日志文件中,打印异常堆栈 + * @param e + * @return + */ + public static String LogExceptionStack(Exception e) { + StringWriter errorsWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(errorsWriter)); + return System.getProperty("line.separator", "/n") + errorsWriter.toString(); + } +} diff --git a/quick-spark/src/main/java/spark/util/ResultStatus.java b/quick-wx-public/src/main/java/com/wx/pn/utils/ResultStatus.java similarity index 98% rename from quick-spark/src/main/java/spark/util/ResultStatus.java rename to quick-wx-public/src/main/java/com/wx/pn/utils/ResultStatus.java index b9022943..02bc8d9d 100644 --- a/quick-spark/src/main/java/spark/util/ResultStatus.java +++ b/quick-wx-public/src/main/java/com/wx/pn/utils/ResultStatus.java @@ -1,4 +1,4 @@ -package spark.util; +package com.wx.pn.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,7 +13,7 @@ public enum ResultStatus { // -1为通用失败(根据ApiResult.java中的构造方法注释而来) FAIL(-1, "common fail"), // 0为成功 - SUCCESS(0, "success"), + SUCCESS(0, "操作成功"), error_pic_file(3,"非法图片文件"), error_pic_upload(4,"图片上传失败"), diff --git a/quick-wx-public/src/main/resources/application.yml b/quick-wx-public/src/main/resources/application.yml new file mode 100644 index 00000000..b39c4d3d --- /dev/null +++ b/quick-wx-public/src/main/resources/application.yml @@ -0,0 +1,101 @@ +server: + port: 8081 + +#logging: +# level: +## com.base.voice.mapper: debug +# com.base.voice.autodialer.mapper: debug + + + +weixin: + app-id: wxd8547a419105c817 + app-secret: c16f43f4e602ee1ddfcc64d01f766fbe + token: aliang + + media: + voice: + template: D:\weixinpath\template\ + path: D:\weixinpath\voice\ + reply-path: D:\weixinpath\voice_reply\ + +#MYBATIS +mybatis: + type-aliases-package: com.base.voice.entity,com.base.voice.autodialer.entity + mapper-locations: classpath*:/mapper/*/*Mapper.xml,classpath*:/mapper/*Mapper.xml + + configuration: + map-underscore-to-camel-case: true + use-generated-keys: true + default-fetch-size: 100 + default-statement-timeout: 30 + +spring: + datasource: +# url: jdbc:mysql://localhost:3306/wx_data?useUnicode=true&characterEncoding=UTF-8 +# username: root +# password: root123 + url: jdbc:mysql://192.168.1.82:3306/autodialer?useUnicode=true&characterEncoding=UTF-8 + username: vector + password: vector + driver-class-name: com.mysql.jdbc.Driver + type: com.alibaba.druid.pool.DruidDataSource + initialSize: 5 + minIdle: 5 + maxActive: 20 + maxWait: 60000 + timeBetweenEvictionRunsMillis: 60000 + validationQuery: SELECT 1 + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + poolPreparedStatements: true + maxPoolPreparedStatementPerConnectionSize: 20 + filters: stat + connectionProperties: + druid.stat.mergeSql: true + druid.stat.slowSqlMillis: 5000 + redis: + database: 1 + host: localhost + port: 6379 + password: +# pool: +# max-active: 8 +# max-wait: -1 +# max-idle: 8 +# min-idle: 0 +# timeout: 500 + +robot: + notify-url: http://192.168.22.2:8081/robot/callNotify + # 最好为插卡数-1 + maximumcall: 2 + is-need-upload-file: false + + conversion: +# v1: +# voice-url: http://192.168.1.31:9840/v1/speech/chart +# text-url: http://192.168.1.31:9840/v1/text/chart + v2: + voice-url: http://192.168.216.216:9841/v2/speech/chat + text-url: http://192.168.216.216:9841/v2/text/chart + v3: + text-url: http://192.168.216.216:9844/v3/text/chat + voice-url: http://192.168.216.216:9843/v3/speech/chat + robot-file-path: + # 0 随机 , 1 男声 , 2 女声 , 3 盖茨比 + mode: 1 + male: /var/smartivr/voice/male + female: /var/smartivr/voice/female + other: /var/smartivr/voice/gcb + # 单位毫秒 + block-asr-duration: 4000 + + + oss: + bucket: bazatest + path: robotrecordfiletest + + + diff --git a/quick-mybatis-druid/src/main/resources/generatorConfig.xml b/quick-wx-public/src/main/resources/generatorConfig.xml similarity index 53% rename from quick-mybatis-druid/src/main/resources/generatorConfig.xml rename to quick-wx-public/src/main/resources/generatorConfig.xml index a739719a..d25e381d 100644 --- a/quick-mybatis-druid/src/main/resources/generatorConfig.xml +++ b/quick-wx-public/src/main/resources/generatorConfig.xml @@ -19,37 +19,44 @@ + + + + connectionURL="jdbc:mysql://192.168.1.82:3306/autodialer?characterEncoding=UTF-8" userId="vector" + password="vector"> - - + + - - - + + + + - + targetPackage="com.base.voice.autodialer.mapper" targetProject="src/main/java"> + + - +
-
diff --git a/quick-wx-public/src/main/resources/logback-spring.xml b/quick-wx-public/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..c62abfcc --- /dev/null +++ b/quick-wx-public/src/main/resources/logback-spring.xml @@ -0,0 +1,56 @@ + + + + logback + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + log/app.log + + logs/app-%i-%d{yyyy-MM-dd}.log + 30 + + + 10MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + ERROR + + log/error.log + + logs/error-%i-%d{yyyy-MM-dd}.gz + 30 + + + 10MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/quick-zookeeper/README.md b/quick-zookeeper/README.md new file mode 100644 index 00000000..432fc84b --- /dev/null +++ b/quick-zookeeper/README.md @@ -0,0 +1,56 @@ +# Zookeeper + +> https://www.w3cschool.cn/zookeeper/zookeeper_overview.html + +### zk集群 +使用docker快速构建一个三个节点的zk集群 +https://hub.docker.com/_/zookeeper +```yml +version: '3.1' + +services: + zoo1: + image: zookeeper + restart: always + hostname: zoo1 + ports: + - 2181:2181 + environment: + ZOO_MY_ID: 1 + ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181 + + zoo2: + image: zookeeper + restart: always + hostname: zoo2 + ports: + - 2182:2181 + environment: + ZOO_MY_ID: 2 + ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=0.0.0.0:2888:3888;2181 server.3=zoo3:2888:3888;2181 + + zoo3: + image: zookeeper + restart: always + hostname: zoo3 + ports: + - 2183:2181 + environment: + ZOO_MY_ID: 3 + ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=0.0.0.0:2888:3888;2181 + +``` + +`docker-compose -f stack.yml up` + +### zkUI +https://github.com/XiaoMi/shepher +使用小米的开源代码作为zk的ui界面 + +注意:我只使用了web镜像,zk和mysql我都是自己部署的 + +或者用这个 https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip +推荐 + +### 其他 +https://www.cnblogs.com/zhaoletian/p/13292250.html \ No newline at end of file diff --git a/quick-zookeeper/pom.xml b/quick-zookeeper/pom.xml new file mode 100644 index 00000000..bc399a0e --- /dev/null +++ b/quick-zookeeper/pom.xml @@ -0,0 +1,40 @@ + + + quick-zookeeper + com.quick + 1.0-SNAPSHOT + 4.0.0 + + + UTF-8 + 1.8 + staging + 3.5.7 + 0.9 + 3.6.1 + + + + + + org.apache.zookeeper + zookeeper + ${zookeeper.version} + + + + + + + + + + org.projectlombok + lombok + 1.18.2 + + + + \ No newline at end of file diff --git a/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKCreate.java b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKCreate.java new file mode 100644 index 00000000..249d47a3 --- /dev/null +++ b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKCreate.java @@ -0,0 +1,41 @@ +package com.quick.zookeeper.client; + +import com.quick.zookeeper.config.ZKConstants; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; + +public class ZKCreate { + + + // create static instance for zookeeper class. + private static ZooKeeper zk; + + // create static instance for ZooKeeperConnection class. + private static ZooKeeperConnection conn; + + // Method to create znode in zookeeper ensemble + public static void create(String path, byte[] data) throws + KeeperException, InterruptedException { + zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + + public static void main(String[] args) { + + // znode path + String path = ZKConstants.ZK_FIRST_PATH; // Assign path to znode + + // data in byte array + byte[] data = "My first zookeeper app".getBytes(); // Declare data + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(ZKConstants.ZK_HOST); + create(path, data); // Create the data to the specified path + conn.close(); + } catch (Exception e) { + System.out.println(e.getMessage()); //Catch error message + } + } +} \ No newline at end of file diff --git a/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKDelete.java b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKDelete.java new file mode 100644 index 00000000..6b11c098 --- /dev/null +++ b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKDelete.java @@ -0,0 +1,27 @@ +package com.quick.zookeeper.client; + +import com.quick.zookeeper.config.ZKConstants; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.KeeperException; + +public class ZKDelete { + private static ZooKeeper zk; + private static ZooKeeperConnection conn; + + // Method to check existence of znode and its status, if znode is available. + public static void delete(String path) throws KeeperException, InterruptedException { + zk.delete(path, zk.exists(path, true).getVersion()); + } + + public static void main(String[] args) throws InterruptedException, KeeperException { + String path = ZKConstants.ZK_FIRST_PATH + "/firstChildren"; //Assign path to the znode + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(ZKConstants.ZK_HOST); + delete(path); //delete the node with the specified path + } catch (Exception e) { + System.out.println(e.getMessage()); // catches error messages + } + } +} diff --git a/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKExists.java b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKExists.java new file mode 100644 index 00000000..f934358b --- /dev/null +++ b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKExists.java @@ -0,0 +1,36 @@ +package com.quick.zookeeper.client; + +import com.quick.zookeeper.config.ZKConstants; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; + +public class ZKExists { + private static ZooKeeper zk; + private static ZooKeeperConnection conn; + + // Method to check existence of znode and its status, if znode is available. + public static Stat znode_exists(String path) throws + KeeperException, InterruptedException { + return zk.exists(path, true); + } + + public static void main(String[] args) throws InterruptedException, KeeperException { + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(ZKConstants.ZK_HOST); + Stat stat = znode_exists(ZKConstants.ZK_FIRST_PATH); // Stat checks the path of the znode + + if (stat != null) { + System.out.println("Node exists and the node version is " + + stat.getVersion()); + } else { + System.out.println("Node does not exists"); + } + + } catch (Exception e) { + System.out.println(e.getMessage()); // Catches error messages + } + } +} \ No newline at end of file diff --git a/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKGetChildren.java b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKGetChildren.java new file mode 100644 index 00000000..33ab11b5 --- /dev/null +++ b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKGetChildren.java @@ -0,0 +1,44 @@ +package com.quick.zookeeper.client; + +import com.quick.zookeeper.config.ZKConstants; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; + +import java.util.List; + +public class ZKGetChildren { + private static ZooKeeper zk; + private static ZooKeeperConnection conn; + + // Method to check existence of znode and its status, if znode is available. + public static Stat znode_exists(String path) throws + KeeperException, InterruptedException { + return zk.exists(path, true); + } + + public static void main(String[] args) throws InterruptedException, KeeperException { + String path = ZKConstants.ZK_FIRST_PATH; // Assign path to the znode + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(ZKConstants.ZK_HOST); + Stat stat = znode_exists(path); // Stat checks the path + + if (stat != null) { + + //“getChildren" method- get all the children of znode.It has two args, path and watch + List children = zk.getChildren(path, false); + for (int i = 0; i < children.size(); i++) + System.out.println(children.get(i)); //Print children's + } else { + System.out.println("Node does not exists"); + } + + } catch (Exception e) { + System.out.println(e.getMessage()); + } + + } + +} \ No newline at end of file diff --git a/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKGetData.java b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKGetData.java new file mode 100644 index 00000000..78e8cba9 --- /dev/null +++ b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKGetData.java @@ -0,0 +1,72 @@ +package com.quick.zookeeper.client; + +import com.quick.zookeeper.config.ZKConstants; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; + +import java.util.concurrent.CountDownLatch; + +public class ZKGetData { + + private static ZooKeeper zk; + private static ZooKeeperConnection conn; + + public static Stat znode_exists(String path) throws + KeeperException, InterruptedException { + return zk.exists(path, true); + } + + public static void main(String[] args) throws InterruptedException, KeeperException { + String path = ZKConstants.ZK_FIRST_PATH; + final CountDownLatch connectedSignal = new CountDownLatch(1); + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(ZKConstants.ZK_HOST); + Stat stat = znode_exists(path); + + if (stat != null) { + byte[] b = zk.getData(path, new Watcher() { + + public void process(WatchedEvent we) { + + if (we.getType() == Event.EventType.None) { + switch (we.getState()) { + case Expired: + connectedSignal.countDown(); + break; + } + + } else { + String path = ZKConstants.ZK_FIRST_PATH; + + try { + byte[] bn = zk.getData(path, + false, null); + String data = new String(bn, + "UTF-8"); + System.out.println(data); + connectedSignal.countDown(); + + } catch (Exception ex) { + System.out.println(ex.getMessage()); + } + } + } + }, null); + + String data = new String(b, "UTF-8"); + System.out.println(data); + connectedSignal.await(); + + } else { + System.out.println("Node does not exists"); + } + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKSetData.java b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKSetData.java new file mode 100644 index 00000000..0fef4196 --- /dev/null +++ b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZKSetData.java @@ -0,0 +1,29 @@ +package com.quick.zookeeper.client; + +import com.quick.zookeeper.config.ZKConstants; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; + +public class ZKSetData { + private static ZooKeeper zk; + private static ZooKeeperConnection conn; + + // Method to update the data in a znode. Similar to getData but without watcher. + public static void update(String path, byte[] data) throws + KeeperException,InterruptedException { + zk.setData(path, data, zk.exists(path,true).getVersion()); + } + + public static void main(String[] args) throws InterruptedException,KeeperException { + String path= ZKConstants.ZK_FIRST_PATH; + byte[] data = "Success".getBytes(); //Assign data which is to be updated. + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(ZKConstants.ZK_HOST); + update(path, data); // Update znode data to the specified path + } catch(Exception e) { + System.out.println(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZooKeeperConnection.java b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZooKeeperConnection.java new file mode 100644 index 00000000..5da3133e --- /dev/null +++ b/quick-zookeeper/src/main/java/com/quick/zookeeper/client/ZooKeeperConnection.java @@ -0,0 +1,37 @@ +package com.quick.zookeeper.client; + +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooKeeper; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + +public class ZooKeeperConnection { + + // declare zookeeper instance to access ZooKeeper ensemble + private ZooKeeper zoo; + final CountDownLatch connectedSignal = new CountDownLatch(1); + + // Method to connect zookeeper ensemble. + public ZooKeeper connect(String host) throws IOException,InterruptedException { + + zoo = new ZooKeeper(host,5000,new Watcher() { + + public void process(WatchedEvent we) { + + if (we.getState() == Event.KeeperState.SyncConnected) { + connectedSignal.countDown(); + } + } + }); + + connectedSignal.await(); + return zoo; + } + + // Method to disconnect from zookeeper server + public void close() throws InterruptedException { + zoo.close(); + } +} \ No newline at end of file diff --git a/quick-zookeeper/src/main/java/com/quick/zookeeper/config/ZKConstants.java b/quick-zookeeper/src/main/java/com/quick/zookeeper/config/ZKConstants.java new file mode 100644 index 00000000..c4e9d40b --- /dev/null +++ b/quick-zookeeper/src/main/java/com/quick/zookeeper/config/ZKConstants.java @@ -0,0 +1,8 @@ +package com.quick.zookeeper.config; + +public class ZKConstants { + public final static String ZK_HOST = "192.168.1.2"; + + public final static String ZK_FIRST_PATH = "/MyFirstZnode"; + +}