Применение | Система не может существовать в изоляции, и даже внутренняя система предприятия неизбежно связана с трехсторонней системой (средствами, SMS, агрегированными данными и т. Д.) Во время процесса разработки. Наша компания является N-партийной платежной системой, которая часто должна быть связана с банками и предприятиями (Alipay, Wechat, Zhongnong Gongjianzhao и т. Д.). Доля и общаться со всеми.
1, используйте общий + режим дизайна для организации
Общая система не может иметь только одну службу, но существует только несколько методов маршрутизации службы, предоставляемых извне.URL-маршрутизацияилиМаршрутизация командного кода. URL-маршрутизация обычно представленаWeChat Pay, в то время как противникAlipayИспользуется метод маршрутизации commandCode. Эти два гиганта индустрии. Можно увидеть, что между двумя методами нет лучшего или худшего. Это просто другая практика. Поэтому, если вместо этого мы разработаем систему, нам не нужно слишком беспокоиться об этом. Оба могут быть использованы.Создавайте супер большие приложения. Однако лично я предпочитаю подход Alipay.
Ладно, давайте отойдем от темы, вернемся к этой статье. Теперь предположим, что существует следующее внешнее приложение, использующее маршрутизацию commandCode (режим маршрутизации URL будет обсуждаться позже), и используйте его в качестве примера для обсуждения следующего: адрес:https://127.0.0.1:1234/openapi/json, список интерфейсов следующий:
- **查询(query)**
请求:
{
"type":"query",
"mchId":"",
"outTradeNo":""
}
响应:略
- **支付(pay)**
请求:
{
"type":"pay",
"mchId":"",
"outTradeNo":"",
"amount":"",
"userId":""
}
响应:略
- **退款(refund)**
请求:
{
"type":"refund",
"mchId":"",
"oriOutTradeNo":"",
"amount":""
}
响应:略
Абстракция + инкапсуляция + наследование
Интерфейс анализа можно увидеть, сообщение запроса для каждого интерфейса существуетtype
,mchId
Так что здесь естественно думать онаследовать.创建一个请求基类BaseRequest, переместите общедоступные поля в этот класс. Но разные типы бизнеса имеют разные типы, как возвращать разные значения. можно использовать здесьабстрактный базовый класс,будетgetTypeМетод определяется какabstract
, специфическая логика может быть реализована унаследованным подклассом. Когда происходит фактический вызов, используется переопределенный способ. Вот пример:
public abstract class BaseRequest {
private String type;
private String mchId;
public abstract String getType();
// 省略剩下的 get、set
}
public class QueryRequest extends BaseRequest {
private String outTradeNo;
@Override
public String getType() {
return "query";
}
}
// 省略 PayRequest、RefundRequest 类定义
Ответ, нехватка места, не расширяет класс запроса с практически таким же подходом.
В ПОРЯДКЕ. На данный момент мы создалиBaseRequest
,BaseResponse
Класс-носитель информации уже есть, и теперь приступаем к написанию основной логики. Я привык называть этот класс xxxClient (почему бы не определить его как xxxManager? Я раскрою его позже). поддерживатьабстракционно-ориентированное программированиеПринципы, формальные параметры и возвращаемые значения, естественно, должны использовать базовые классы для достижения цели унифицированного вызова:
public class xxxClient {
public BaseResponse execute(BaseRequest request) {
// 1、签名(如果需要)
// 2、RequestBean 转 JSON
// 3、发送请求
// 4、验签(如果需要)
// 5、JSON 转 ResponBean
}
}
Комментарии примерно список того, что нужно сделать метод. Логика проверки подписи опущена здесь, и каждая система имеет различные требования для подписей. Важно преобразовать эти два шага. При запросе вам необходимо преобразовать запрос в формат, необходимый док-станцией; при реагировании необходимо преобразовать возвращенный контент во внутренний респонсовый пример.
По вышеприведенному интерфейсу формат json (XML будет рассмотрен позже), а вложенности нет. Можно сказать, что очень просто. Можно напрямую использовать готовую библиотеку json (fastjson, jackson, gson , и т.д.). Взяв в качестве примера fastjson, когда RequestBean необходимо преобразовать в строку json, вызовитеJSON.toJSONString()
Метода достаточно.Если вам нужно проигнорировать поле или переименовать поле, вы также можете использовать предоставленные им аннотации, чтобы хорошо завершить работу, что не будет здесь обсуждаться.
Существует проблема, когда необходимо передать возвращенный пакет в ответ на республику. Как мне узнать, какой респондент? КонечноgetType
Метод получает текущий код команды, а затем находит тип класса, соответствующий ResponBean, в соответствии с кодом команды. Псевдокод выглядит следующим образом:
private Class<? extends BaseResponse> getTypeByCommandCode(String code) {
if ("query".equals(code)) {
return QueryResponse.class;
}
else if ("pay".equals(code)) {
return PayResponse.class;
}
// ....
}
Этого можно добиться, но это многоif else
Немного неэлегантно, не так ли? Хороший код должен следоватьОткрыто закрыто, открыт для расширения, закрыт для модификации.
Нетрудно обнаружить, что ResponseBean основан на типе, а тип уникален для каждого RequestBean. В этом случае мы можем обращаться с классом ResponseBean как со значением типа. Также вBaseRequest
Определите абстрактный метод в ResponseBean, чтобы возвращать класс ResponseBean, чтобы можно было опустить суждение, если еще. код показывает, как показано ниже:
public abstract class BaseRequest {
private String type;
private String mchId;
public abstract String getType();
// 获取对应 ResponseBean 的 Class
public abstract Class<? extends BaseResponse> getResponseType();
// 省略剩下的 get、set
}
public class QueryRequest extends BaseRequest {
private String outTradeNo;
// 返回 QueryResponse 的 class
@Override
public Class<? extends BaseResponse> getResponseType() {
return QueryResponse.class;
}
@Override
public String getType() {
return "query";
}
}
пройти черезобщая квалификациябудетgetResponseType
Диапазон возвращаемого значения метода закреплен наBaseResponse
Подкласс класса, код также становится более надежным. При вызове в xxxClient это может быть так:
public class xxxClient {
public BaseResponse execute(BaseRequest request) {
// ...省略前面的步骤
String response = "假设这是三方系统返回内容";
BaseResponse responseBean = JSON.parseObject(response, request.getResponseType());
return responseBean;
}
}
добавить дженерики
Код написан здесь, и единый вызов был достигнут, и он следует принципу открытого-закрытого. Клиент новый другой RequestBean, вызовexecute
метод, получите экземпляр возвращаемого значения базового класса BaseResponse, а затем передайтесильный поворотПолучите конкретный пример ответа подкласса и выполните соответствующую бизнес-логику. Например, код вызывающей стороны запроса примерно такой:
public static void main(String[] args) {
QueryRequest request = new QueryRequest();
QueryResponse response = (QueryResponse)new xxxClient().execute(request);
// 做其他的业务逻辑
}
Вы спросите, форсировать этот путь несколько надуманно и недружелюбно к звонящему, может есть более изящный способ? Конечно есть, тогда нужно использоватьДженерики.
После тщательного наблюдения обнаружено, что один запрос в ближайшее время соответствует только одному откликам, и двое соответствуют один за другим, поэтому мы можем ввести дженерики и использоватьT
Вместо общих качеств?
Непосредственно определите их на уровне класса вместо метода, чтобы определить тип базового ответа при определении RequestBean. Как показано ниже:
public abstract class BaseRequest<T extends BaseResponse> {
private String type;
private String mchId;
public abstract String getType();
// 获取对应 ResponseBean 的 Class
public abstract Class<T> getResponseType();
// 省略剩下的 get、set
}
В этот моментQueryRequest
Определение класса необходимо изменить, как это:
public class QueryRequest extends BaseRequest<QueryResponse> {
private String outTradeNo;
// 返回 QueryResponse 的 class
@Override
public Class<QueryResponse> getResponseType() {
return QueryResponse.class;
}
@Override
public String getType() {
return "query";
}
}
После модификации вы обнаружите, что связь между RequestBean и ResponseBean стала более тесной, а последующее расширение более дружественным. Конечно, это предзнаменование не рассчитано на тесную связь, важно увидеть, как его трансформировать в классе xxxClient:
public class xxxClient {
public <T extends BaseResponse> T execute(BaseRequest<T> request) {
// ...省略前面的步骤
String response = "假设这是三方系统返回内容";
T responseBean = JSON.parseObject(response, request.getResponseType());
return responseBean;
}
}
потому что использоватьT
Вместо конкретного типа подкласса ResponseBean вы можете напрямую использоватьexecute
Метод возвращает значение дляT
При использовании общих квалификаторов,BaseRequest
Нужно принести на урокT
тип.这时候,调用方代码就无需再做强转这个动作,非常的合理和优雅:
public static void main(String[] args) {
QueryRequest request = new QueryRequest();
QueryResponse response = new xxxClient().execute(request);
// 做其他的业务逻辑
}
На данный момент классовая организация модуля в основном завершилась. Если необходимо преобразовать или улучшить рамку на основе этого, это также очень просто, например, следующие две ситуации:
если XML
По сравнению с JSON это не более чем еще одна форма представления. Просто измените библиотеку классов, обычно используемые Xstream, dom4j и так далее. Я не об этом хочу здесь говорить.Если стыковочное приложение поддерживает несколько форматов, как должен быть организован код в это время?
Организация класса остается примерно такой же, и это становится просто преобразованием Бина. Так почему бы не вынести действие конвертации и указать его самим клиентом. Определите интерфейс преобразования следующим образом:
public interface Converter {
<A extends BaseRequest<?>> String bean2String(A request);
<B extends BaseResponse> B string2Bean(String responStr, Class<B> clazz);
}
использовать конвертер какxxxClient#execute
Передаваемые параметры метода
public <T extends BaseResponse> T execute(BaseRequest<T> request, Converter converter) {
String requestStr = converter.bean2String(request);
String response = "假设这是三方系统返回内容";
T responseBean = converter.string2Bean(response, request.getResponseType());
return responseBean;
}
Для группы действий преобразования делается абстракция, когда требуется расширение формата, никаких изменений не требуется.xxxClient#execute
Для кода бизнес-логики нужно только добавить конвертер или принцип открыто-закрыто.
public class XmlConverter implements Converter {
@Override
public <A extends BaseRequest<?>> String bean2String(A request) {
// 这里省略 XmlUtils 具体实现...
return XmlUtils.toString(request);
}
@Override
public <B extends BaseResponse> B string2Bean(String responStr, Class<B> clazz) {
// 这里省略 XmlUtils 具体实现...
return XmlUtils.toBean(responStr, clazz);
}
}
Если это маршрут URL
В начале мы говорили о двух формах маршрутизации. Все мы говорили об использовании commandCode для маршрутизации. А как насчет URL-маршрутизации?
С этой полкой это уже простое дело. Небольшая интерпретация заключается в том, что URL-адрес каждого бизнеса отличается, тогда мы можем обработать его в соответствии с методом типа обработки и классом BeanResponse.
public abstract class BaseRequest<T extends BaseResponse> {
private String type;
private String mchId;
private String url;
// 不同业务具有不同的请求地址
public abstract String getUrl();
public abstract String getType();
// 获取对应 ResponseBean 的 Class
public abstract Class<T> getResponseType();
// 省略剩下的 get、set
}
public class QueryRequest extends BaseRequest<QueryResponse> {
private String outTradeNo;
@Override
public String getUrl() {
return "https://127.0.0.1:1234/openapi/query";
}
@Override
public Class<QueryResponse> getResponseType() {
return QueryResponse.class;
}
@Override
public String getType() {
return "query";
}
}
Просто измените логику xxxClient, чтобы получить URL-адрес, который примерно выглядит следующим образом:
public <T extends BaseResponse> T execute(BaseRequest<T> request, Converter converter) {
String requestStr = converter.bean2String(request);
HttpUtils.doPost(request.getUrl(), requestStr, 5, 15);
String response = "假设这是三方系统返回内容";
return converter.string2Bean(response, request.getResponseType());
}
Конечно, вот только два примера. Реальный бизнес будет сложнее, например, есть несколько методов подписи, один запрос соответствует нескольким ответам и т. д. Их можно расширить на основе этой полки.
2. Минимизируйте внешние зависимости
На мой взгляд, четко видны границы каждого модуля.Когда вы пишете модули, которые взаимодействуют со сторонней системой, вы не должны полагаться на персистентный слой или классы веб-слоя.Это также соответствуетПринцип единой ответственности.
Обработка зависимостей Spring
Прежде всего, вы не можете полагаться на классы, связанные со Spring. Если вы полагаетесь на него, он будет загрязнен. Если вам нужно перенести модуль в проект, который не использует Spring, что вы должны делать? Размещать его в Spring или нет, должен решать верхний уровень, то есть вызывающая сторона.Согласно моему предположению, также будет класс xxxManager для обработки действия соединения со Spring.В этом диспетчере текущее ограничение и слияние можно и обработать..
Предоставленная обработка зависимостей SDK
Также есть некоторые приложения-доки, которые «предлагают» использовать предоставленный SDK (в виде jar)! Обязательно обратите внимание!Не используйте его столько, сколько сможете. Если вы решите им поверить, то последующее управление jar (загрузка приватных серверов) и расширение функций сведут вас с ума. В качестве простого примера предположим, что все файлы конфигурации в вашем проекте хранятся в базе данных, что позволяет реализовать функцию динамического обновления.Большая часть SDK, предоставляемая приложением стыковки, будет хранить конфигурацию в виде файла. В этом случае рекомендуется изменить SDK или переупаковать его самостоятельно.
Если вы действительно не можете изменить чужой SDK, вы можете только скомпрометировать, пожалуйста, используйтезависимости исходного кодапуть вместо зависимостей формы Maven. Это декомпиляция чужих SDK для получения файлов .java, а затем копирование их в собственный каталог исходного кода приложения.
Конечно, вы столкнетесь с непростой ситуацией, файлы .jar, предоставленные внутри, содержат сотни классов, один декомпилятор Не было бы так... К счастью, есть инструменты jd-gui, он поддерживает open.jar при этом время сохранить все источники.
3, переменные конфигурации параметризованы
Переменные конфигурации, аналогичныеURL-адрес запроса, время ожидания сетевого запроса, кодировка символовИ так далее, они должны предоставлять параметры внешнему миру. Предполагая, что другое приложение имеет несколько сред, после того, как оно будет записано в коде, гибкость программы значительно снизится. Существует также период ожидания, который должен определяться бизнесом, например, период ожидания запроса и период ожидания транзакции должны быть разными. Поэтому рекомендуется сделать это в форме параметра, который решает вызывающая сторона.
4. Возвращаемая стоимость упаковки
Предполагается, что есть такая ситуация, и подпись трехсторонней системы не удалась.Как сделать возврат, чтобы звонил неизвестный эта ошибка? Это действительно хлопотно, невозможно добиться того, чтобы процесс мог получить ответ тройной системы, даже если он будет отправлен, это тоже возможно. Для этих обстоятельств мы можем определить класс упаковки следующим образом:
public class ManagerResultWrapper<T> {
/** 提示信息 */
private String info;
/** 成功标识 */
private boolean success;
/** 返回的错误码 */
private String code;
/** 需要返回的数据 */
private T data;
private ManagerResultWrapper(String info, boolean success, String code, T data) {
this.info = info;
this.success = success;
this.code = code;
this.data = data;
}
public static <T> ManagerResultWrapper<T> success(T data) {
return new ManagerResultWrapper<>("", true, "", data);
}
public static <T> ManagerResultWrapper<T> fail(String code, String info) {
return new ManagerResultWrapper<>(info, false, code, null);
}
// 省略 get、set
}
Поле успеха может быть указано, чтобы определить, была ли передача в банк успешной, поля кода и информации содержат информацию об ошибке, а общее T содержит сущность, на которую ответил банк (определено выше).BaseResponse
). Конкретные правила возврата могут быть настроены в соответствии с реальной ситуацией.
5, ненормальная обработка
Лично я не люблю использовать исключения для логики управления, но взаимодействие трехпартийной системы связано с участием сети IO, которая не может быть сбежена и должна быть решена. Моя привычка - не поймать Ио Исключения, так что вызывающий абонент знает, что в настоящее время имеется IO взаимодействие в это время. В то же время не рекомендуется обернуть и возвращать исключения IO. Если внешний мир будет очень чувствительным к IO Исключения (например,ConnectTimeoutException
,ReadTimeoutException
может привести к другим бизнес-результатам), то здесь неуместно выбирать исключение для упаковки.
Это программная привычка. Упаковка на самом деле есть, но вам нужно идентифицировать различные исключения (такие как: В пользовательском классе аномалии добавить поле КОД), чтобы даже если у вызывающей стороны есть ненормальные требования к различению, это может быть удовлетворено.
6. Ввод и вывод должны иметь полную регистрацию
Об этом не нужно говорить больше, но все же упомянуть об этом. Когда есть реальная бизнес-проблема и противник сражается, бревно является мощным оружием, пожалуйста, используйте бревно, чтобы говорить. Не беспокойтесь о чрезмерном объеме журнала.Какой возраст, не ELK утром?
7. Более полные примеры использования и модульные тесты
Проведите модульное тестирование, с одной стороны, это может улучшить качество вашего рабочего задания, а с другой стороны, его можно рассмотреть для ваших товарищей по команде. Уровень неравномерный, и товарищи по команде могут не понять ваш дизайн.Когда есть примеры использования или модульные тесты, это может значительно повысить эффективность работы. (документация тоже работает)
Опять же, машина может читать, может писать код, это не сложно, писать нам (людям) можно читать код, это истинный уровень.