PayMap
PayMap 是一个使用 Java 语言集成三方支付的小 Demo, 现已集成支付宝 (国内国际移动端 PC 端) 微信银联 (ACPUPOP) 光大 (网关网页) 邮政支付, 采用的技术栈为: SpringMVC+Spring+MyBatis+Shiro+RabbitMQ+Redis
特性
支持前面提到的各种 ** 支付
支付请求调用支持 HTTP 和异步 MQ
控制层统一异常处理
LogBack 日志记录
Redis 缓存机制
Shiro 安全机制
MyBatis 代码自动生成
HTTP 请求日志记录
RESTful APIs
说明
1 本文项目来自 Martin404, 自己只是临摹大佬的项目
2 重要的是学习过程, 而不是结果但, 结果同样重要, 加油 gogogo
3 框架搭建就略过了配置文件太多遇到的时候贴出来也收藏起来, 留着备用
4GistInsight.io for GitHub 必备吧, 划词翻译不懂的单词划一划
5 在 IDEA 中我会注重代码规范, 但是这里为了节约地方, 能省的就省略了还请谅解
6 代码提交到这里了 GitHub 根据提交记录找自己想要的类库
7 重要的在后面, 一切都只只是刚刚开始(希望不要被屏蔽)!!gogogo
2 核心包~common.dao,service,web,mq.
(1)我们先从 dao 开始吧, 这里也可以是 web 熟悉的来了, IBaseMapper 还是定义基础接口, 但与中不同的是这个用泛型修饰, 为什么呢? 先看一张图
图片 + 代码可以说明一切问题反射 + 泛型很重要是还不懂, 再用文字描述
1 泛型类, 是在实例化类的时候指明泛型的具体类型; 支持创建可以按类型进行参数化的类
2 泛型方法, 是在调用方法的时候指明具体的类型
public List<T> findAll() {}
3 泛型接口, JDK,Spring 中大量运用泛型
总结来说泛型可以提高 Java 程序的类型安全, 所有的类型转换都是自动和隐式的为优化, 性能带来收益
IBaseDao 接口:
- /**
- * 持久层通用接口
- */
- public interface IBaseDao<T> {
- void save(T entity);
- void delete(T entity);
- void update(T entity);
- T findById(Serializable id);
- List<T> findAll();
- }
BaseDaoImpl 实现类:
- /**
- * 持久层通用实现
- */
- public class BaseDaoImpl<T> extends HibernateDaoSupport implements IBaseDao<T> {
- // 代表的是某个实体的类型
- private Class<T> entityClass;
- @Resource// 根据类型注入 spring 工厂中的会话工厂对象 sessionFactory
- public void setMySessionFactory(SessionFactory sessionFactory){
- super.setSessionFactory(sessionFactory);
- }
- // 在父类 (BaseDaoImpl) 的构造方法中动态获得 entityClass
- public BaseDaoImpl() {
- ParameterizedType superclass = (ParameterizedType) this.getClass().getGenericSuperclass();
- // 获得父类上声明的泛型数组
- Type[] actualTypeArguments = superclass.getActualTypeArguments();
- entityClass = (Class<T>) actualTypeArguments[0];
- }
- public void save(T entity) {
- this.getHibernateTemplate().save(entity);
- }
- public T findById(Serializable id) {
- return this.getHibernateTemplate().get(entityClass, id);
- }
- public List<T> findAll() {
- String hql = "FROM" + entityClass.getSimpleName();
- return (List<T>) this.getHibernateTemplate().find(hql);
- }
- }
(2)接下来, 再看本项目中定义 IBaseMapper
@SelectProvider 注解用于生成查询用的 sql 语句, 有别于 @Select 注解,@SelectProvide 指定一个 Class 及其方法, 并且通过调用 Class 上的这个方法来获得 sql 语句
@ResultMap 注解用于从查询结果集 RecordSet 中取数据然后拼装实体 bean
@SelectProvide 方法, 如果参数使用了 @Param 注解, 那么参数在 Map 中以 @Param 的值为 key
在这里使用了注解的形式, 但是也可以在 XMl 方法配置
- public interface IBaseMapper<T> {
- @SelectProvider(type = MapperProvider.class, method = "dynamicSQL")
- T selectOne(T record);
- @SelectProvider(type = MapperProvider.class, method = "dynamicSQL")
- T selectByPrimaryKey(Object key);
- @InsertProvider(type = MapperProvider.class, method = "dynamicSQL")
- int insert(T record);
........................... 忽略了几个.......................
- @DeleteProvider(type = MapperProvider.class, method = "dynamicSQL")
- int delete(T record);
- @DeleteProvider(type = MapperProvider.class, method = "dynamicSQL")
- int deleteByPrimaryKey(Object key);
- @DeleteProvider(type = MapperProvider.class, method = "dynamicSQL")
- int deleteByExample(Object example);
- @UpdateProvider(type = MapperProvider.class, method = "dynamicSQL")
- int updateByExample(@Param("record") T record, @Param("example") Object example);
- List<T> getAllByPage(RowBounds rowBounds); // 这个是用于分页的
- }
(3)中间插一个 RabbitMQ MSG 序列化 JSON 转换器, 主要作用是客户端和服务端需要传输 Json 格式的数据包, 所以需要进行转换这个工具包也是必备的之一友情提示, 安装 MQ 时, 一定要以系统管理员运行 CMD
RabbitMQ 已经实现了 Jackson 的消息转换(Jackson2JsonMessageConverter), 由于考虑到效率, 如下使用 Gson 实现消息转换
如下消息的转换类的接口 MessageConverter,Jackson2JsonMessageConverter 的父类 AbstractJsonMessageConverter 针对 json 转换的基类
我们实现 Gson2JsonMessageConverter 转换类也继承 AbstractJsonMessageConverter
为了节约地方, 代码放 Gist 了, 需要的时候直接去找
- /**
- * MQ MSG 序列化 JSON 转换器
- */
- public class Gson2JsonMessageConverter extends AbstractJsonMessageConverter {
- private static Logger logger = LoggerFactory.getLogger(Gson2JsonMessageConverter.class);
- private static ClassMapper classMapper = new DefaultClassMapper();
- private static Gson gson = new Gson();
- @Override
- protected Message createMessage(Object object, MessageProperties messageProperties) {
- byte[] bytes = null;
- try {
- String jsonString = gson.toJson(object);
- jsonString.getBytes(getDefaultCharset());
- }
- catch (IOException e) {
- new MessageConversionException("Failed to convert Mesage context",e);
- }
- messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
- messageProperties.setContentEncoding(getDefaultCharset());
- if (bytes != null) {
- messageProperties.setContentLength(bytes.length);
- }
- classMapper.fromClass(object.getClass(),messageProperties);
- return new Message(bytes,messageProperties);
- }
- @Override
- public ClassMapper getClassMapper() {
- return new DefaultClassMapper();
- }
- }
(4)接下来就是定义基础 IBaseService 以及实现类
- public interface IBaseService<T> {
- /**
- * 根据主键查询指定实体
- */
- T getId(Object id) ;
- List<T> getByEntiry(T entity);
- PageInfo<T> getByPage(RowBounds rowBounds);
- int save(T entity);
- int update(T entity);
- int delete(Object id);
- int saveSelective(T entity) throws DBException;
- int updateSelective(T entity);
- }
IBaseService: 全部代码在这 Gist
- /**
- * Created by guo on 3/2/2018.
- */
- public abstract class BaseService<T> implements IBaseService<T> {
- private static Logger logger = LoggerFactory.getLogger(BaseService.class);
- @Resource
- protected RabbitTemplate amqpTemplate;
- @Autowired
- protected RedisTemplate redisTemplate;
- public abstract IBaseMapper<T> getBaseMapper();
- /**
- * 根据主键查询指定实体
- * @param id
- * @return
- */
- @Override
- public T getId(Object id) {
- return this.getBaseMapper().selectByPrimaryKey(id);
- }
- /**
- * 获取分页数据
- */
- @Override
- public PageInfo<T> getByPage(RowBounds rowBounds) {
- List<T> list = this.getBaseMapper().getAllByPage(rowBounds);
- return new PageInfo<T>(list);
- }
- /**
- * 保存对象, 保存所有属性
- */
- @Override
- public int save(T entity) {
- return this.getBaseMapper().insert(entity);
- }
- /**
- * 删除指定数据
- */
- @Override
- public int delete(Object id) {
- return this.getBaseMapper().deleteByPrimaryKey(id);
- }
- /**
- * 更新对象, 值更新对象中不为 Null 的属性, 主键不能为 NULL
- */
- @Override
- public int updateSelective(T entity) {
- return this.getBaseMapper().updateByPrimaryKeySelective(entity);
- }
- }
(5)接下来就是 web 包中的内容, 涉及监听器和过滤器
- /**
- * 系统初始化监听器, 在系统启动时运行, 进行一些初始化工作
- */
- public class InitListener implements javax.servlet.ServletContextListener {
- private static Logger logger = LoggerFactory.getLogger(InitListener.class);
- public static ApplicationContext context;
- public void contextDestroyed(ServletContextEvent arg0) {
- }
- public void contextInitialized(ServletContextEvent servletContextEvent) {
- context = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContextEvent.getServletContext());
- // 加载银联 upop 配置文件
- SDKConfig.getConfig().loadPropertiesFromSrc();
- String proPath = servletContextEvent.getServletContext().getRealPath("/");
- SDKConfig config = SDKConfig.getConfig();
- config.setSignCertDir(proPath + config.getSignCertDir());
- config.setSignCertPath(proPath + config.getSignCertPath());
- config.setValidateCertDir(proPath + config.getValidateCertDir());
- // 缓存初始化忽略
- }
- }
过滤器:
我们先看关于日志的, 真心看不懂, 后面有一大堆代码地址
- /**
- * request response log 记录过滤器
- */
- public class LoggingFilter extends OncePerRequestFilter {
- protected static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
- private static final String REQUEST_PREFIX = "Request:";
- private static final String RESPONSE_PREFIX = "Response:";
- private AtomicLong id = new AtomicLong(1);
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
- if (logger.isDebugEnabled()) {
- long requestId = id.incrementAndGet();
- request = new RequestWrapper(requestId, request);
- }
- try {
- filterChain.doFilter(request, response);
- } finally {
- if (logger.isDebugEnabled()) {
- logRequest(request);
- }
- }
- }
- //...
- }
还有一个请求包装类, 和响应类代码地址
- public class RequestWrapper extends HttpServletRequestWrapper {
- private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- private long id;
- public RequestWrapper(Long requestId, HttpServletRequest request) {
- super(request);
- this.id = requestId;
- }
- @Override
- public ServletInputStream getInputStream() throws IOException {
- return new ServletInputStream() {
- private TeeInputStream tee = new TeeInputStream(RequestWrapper.super.getInputStream(), bos);
- @Override
- public int read() throws IOException {
- return tee.read();
- }
- };
- }
- public byte[] toByteArray() {
- return bos.toByteArray();
- }
- }
- ---------------------------------------------------------------------------------
- public class ResponseWrapper extends HttpServletResponseWrapper {
- private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- private PrintWriter writer = new PrintWriter(bos);
- private long id;
- public ResponseWrapper(Long requestId, HttpServletResponse response) {
- super(response);
- this.id = requestId;
- }
- @Override
- public ServletOutputStream getOutputStream() throws IOException {
- return new ServletOutputStream() {
- private TeeOutputStream tee = new TeeOutputStream(ResponseWrapper.super.getOutputStream(), bos);
- @Override
- public void write(int b) throws IOException {
- tee.write(b);
- }
- };
- }
- @Override
- public PrintWriter getWriter() throws IOException {
- return new TeePrintWriter(super.getWriter(), writer);
- }
- public byte[] toByteArray() {
- return bos.toByteArray();
- }
- }
这一块只是得补补, 用到的时候再看, 还有一个 TeePrintWriter
核心包的东东算是完了, 重点是在 IBaseMapperIBaseService 的设计这里用到了泛型, 还有 Mybatis3.X 新特性, 基于注解的其实完全可以用 XML 配置文件
gogogo 正式进入业务逻辑部分
来源: https://juejin.im/post/5a99323e518825557c00ea73