Java系统开放接口统一网关设计

本文主要讲解开放接口设计,主要是以SpringBoot web 项目,基于自定义注解+反射+非对称加密RSA签名等实现的灵活的统一开放接口设计,文末附源码地址。

1.背景

互联网公司随着业务的发展,系统间或多或少会开放一些对外接口,这些接口都会以API的形式提供给外部。
为了方便统一管理,统一鉴权,统一签名认证机制,流量预警等引入了统一网关。API网关是一是对外接口唯一入口。

2.开放接口需要有哪些功能?需要注意什么?

除了业务功能还需要有

  • 统一鉴权
  • 流量监控
  • 路由分发
  • 架构简单易懂,方便维护
  • 方便追加功能,不需要重复写鉴权等逻辑代码

对外开放的接口,数据安全性是第一位

  • 如何保证安全通信,防止数据被恶意篡改等攻击呢?
  • 怎么证明是你发的请 求呢?

比较流行的方式一般是

  • 加密 -> 密文传输,接收方需要解密
  • 加签 -> 双方建立秘钥-请求方加签,接收方验签防止数据中途被篡改

# 3.开放接口设计 **本文用到的主要技术点**

  • 反射机制
  • RSA签名算法
  • SpringBoot
  • Hibernate-validator注解式参数校验

3.1统一网关接口参数

公共参数

参数 类型 是否必填 最大长度 描述
app_id String 32 业务方appId
method String 200 请求方法
version String 10 默认:1.0
api_request_id String 32 随机请求标识,用于区分每一次请求
charset String 16 默认:UTF-8
sign_type String 10 签名类型:RSA或RSA2
sign String - 签名
content String - 业务内容 :json 格式字符串

返回内容

参数 类型 是否必填 最大长度 描述
success boolean 16 是否成功
data Object - 返回业务信息(具体见业务接口)
error_code String 10 错误码(success为false时必填)
error_msg String 128 错误信息码(success为false时必填)

**

3.1签名规则

这里是使用的是RSA签名
规则如下:

  • 签名参数剔除sign_type 、 sign ;
  • 将剩余参数第一个字符按照ASCII码排序(字母升序排序),遇到相同字母则按第二个字符ASCII码排序,以此类推;
  • 将排序后的参数按照组合“参数=参数值”的格式拼接,并用&字符连接起来,生成的字符串为待签名字符串;
  • 使用RSA算法通过私钥生成签名

RSA === SHA1 --> base64
RSA2 === SHA256 --> base64

3.3代码实践

注:源码见文章末,依赖这里只简单列出了父pom中部分依赖,,详见源码
工程结构图:

3.3.1Maven依赖

注:依赖这里只列举了一部分,文末附源码地址
open-api-project > pom

<properties><project.version>1.0.0</project.version><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><fasterxml.version>2.10.2</fasterxml.version><commons-lang3.version>3.4</commons-lang3.version><commons-collections.version>3.2.2</commons-collections.version><fastjson.version>1.2.70</fastjson.version><slf4j.version>1.7.25</slf4j.version><guava.version>21.0</guava.version><javax.servlet.version>3.1.0</javax.servlet.version><alipay-sdk.version>4.8.10.ALL</alipay-sdk.version><hutool.version>5.0.0</hutool.version><lombok.version>1.16.4</lombok.version><hibernate-validator.version>5.4.1.Final</hibernate-validator.version></properties><!-- 依赖管理 --><dependencyManagement><dependencies><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${fasterxml.version}</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>${commons-lang3.version}</version></dependency><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>${commons-collections.version}</version></dependency><!--fastjson json--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version></dependency><!-- guava --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>${guava.version}</version></dependency><!-- 校验工具 --><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>${hibernate-validator.version}</version></dependency><dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>${alipay-sdk.version}</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version></dependency><!-- lombok   --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency></dependencies></dependencyManagement>

open-api-web > pom

<dependencies><dependency><groupId>com.open.api</groupId><artifactId>open-api-common</artifactId><version>${project.version}</version></dependency><!-- spring boot starters --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-test</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

3.3.2配置文件

server.port=8821#日志配置
logging.level.root=WARN
logging.level.net.sf=WARN
logging.level.com.open.api=debug#是否校验签名
open.api.common.key.isCheckSign=true#开放接口公钥
open.api.common.key.publicKey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwdK0le7UypaYWEWBoQkGTpu2nlYnM+iX8pa7Gz9neSnANfcuxrMgmmXrG+Dw6f3OQpiHl4mbKelyVjJTBLh4cvo1am2OSZvBjefZNshphx4ctBtx6BpGIRwlTvJRsjajMCY3RyF6px+Ehz0zeDBf7w2M6GZnSv2YhPp2YIZZo/01GYVJ4RgzzfkEEKyC+96+shqANHVOaiiG4byMJL8zv9q3kshSNCA1NT8r7toq8wPYhUKwCas/i5GauyRCIX+KhCpD9+/HTkFmr0PUWoNIZ61lRpTMbiTfDWU/5tJ3UPwdk6oVM3ZwkLoBAO8HXHvPk6avCupXq63u4wGrn30eiwIDAQAB#开放接口私钥
open.api.common.key.privateKey=MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDB0rSV7tTKlphYRYGhCQZOm7aeVicz6JfylrsbP2d5KcA19y7GsyCaZesb4PDp/c5CmIeXiZsp6XJWMlMEuHhy+jVqbY5Jm8GN59k2yGmHHhy0G3HoGkYhHCVO8lGyNqMwJjdHIXqnH4SHPTN4MF/vDYzoZmdK/ZiE+nZghlmj/TUZhUnhGDPN+QQQrIL73r6yGoA0dU5qKIbhvIwkvzO/2reSyFI0IDU1Pyvu2irzA9iFQrAJqz+LkZq7JEIhf4qEKkP378dOQWavQ9Rag0hnrWVGlMxuJN8NZT/m0ndQ/B2TqhUzdnCQugEA7wdce8+Tpq8K6lerre7jAauffR6LAgMBAAECggEBAIv3vF9V5KcUD5oXP6BqIvrbagp3zsGmoywVe7MWm4OdCeguw8HME6xME3fDflaL6cqf2bMuNTYUFnR2zQroqFrno3FjAlDXwPPYTT1JhyODNFlARIbHioNYjvyu8x5OZJRd1KdyXt+XXB5JrQSLcovwbiRZ5xf5gI3vTVMxUkSgTxf2P2smaXLZ2k6epSlvFr8u+SJaGOgjKCvbGf+jXyL0L8kntukNLocnSXU3sfFmAmd87DxPFdXAFDnS09tWOcjHfIZmwjHMX3qVP/2jj1DWOjIW0Ow/VRegbYTpLmSQTMcOUaFRprvwd0ZKaZ0aQMNPqPrqkHzrfQsnfjxY+akCgYEA+vbt40rIBDsN5HCPGbyDBU0/+A3wsGh4nqvY9JKACaMpg/FvyMz37GpL8AOMy/mUCVXjVyMoNUFZf/fEhMblBuYQBmgMVk1IQaVESvUlZ33Vgot0TU8YHY2Hpk541e3vKL0X0X6XLgS6CZ63cMx04uZxoFEWlJJm/qqLru3MQp8CgYEAxbZFVgnQ9XTQlHHgpUiS/R7qHo5joBDzlF+m29CYplI5nJUmntoChnHZ6RBoiPW58A3NJOlfIL6J2+Mwwd6kHWD2DSwVDRfk7Hb5Dw6o+tOf+In+zuPZtopr6L7oiKQtwXtGV88ZhDdqX9z5ge9EYP4Psd0Mfchv5vkJENreqJUCgYEAw8easTQHcXV4UvuURymOtLYc7zBA0f3OC0pYiAM5q0sD+hCBeg6cYmxSLT03u3BKEjZUkge1OEZwwanSPxrCVG1plvXYmgLUGZIKAsfXlDLQO3T7F8xaLcPZTN3u2kUxy4Aocp/k5Ft/nj2ZMX/ut4u6nKxlhyXm/0igi6irLlUCgYAWpalNkLRJ2Yam6mB0Llr/+ZGRzHem9yofndFMLpm9u39z6zXQTmKpqdLvOnzu607QK5SLHNxTsN+zu1NzcaBU6S1mFt2WcV08pOgkjGZYzPLvEkeIxVrD6RkxQOT7+epv1kIZftSKa5qYvoQqGRE5FwEPO6XZpqMCzxX1w0xr/QKBgDeom9MiI9a125jr/n9ghSWfvaxCXgPrdojr8QZrlo028iT711ND8QUwCbb9GDr+pyXesANCm78zhdltfeNFimEUktyS0F8Li2+GbYjTvLNXtwxTKZcOXRR+MC5bMHq4hY/+71NhnGazy3yidHn0doReezqGvkotJuRJSr+l1qmU

3.3.3自定义注解

作用说明:自定义注解主要用于标记开放接口
开放接口方法注解

/*** 开放接口注解** @author 程序员小强*/
@Documented
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenApi {/*** api 方法名*/String method();/*** 方法描述*/String desc() default "";
}

开放接口实现类注解

/*** 开放接口实现类注解** @author 程序员小强*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpenApiService {}

3.3.4API接口初始化容器与扫描器

作用说明:在项目启动的时候,将添加自定义注解的实现加载到容器

/*** Api 初始化容器*/
@Service
public class ApiContainer extends HashMap<String, ApiModel> {}
/*** api接口对象*/
public class ApiModel {/*** 类 spring bean*/private String beanName;/*** 方法对象*/private Method method;/*** 业务参数*/private String paramName;public ApiModel(String beanName, Method method, String paramName) {this.beanName = beanName;this.method = method;this.paramName = paramName;}//省略 get/set
}
//导包 太长,这里省略了,详见源码/*** Api接口扫描器** @author 程序员小强*/
@Component
public class ApiScanner implements CommandLineRunner {private static final Logger LOGGER = LoggerFactory.getLogger(ApiScanner.class);/*** 方法签名拆分正则*/private static final Pattern PATTERN = Pattern.compile("\\s+(.*)\\s+((.*)\\.(.*))\\((.*)\\)", Pattern.DOTALL);/*** 参数分隔符*/private static final String PARAMS_SEPARATOR = ",";/*** 统计扫描次数*/private AtomicInteger atomicInteger = new AtomicInteger(0);@Resourceprivate ApiContainer apiContainer;@Overridepublic void run(String... var1) throws Exception {//扫描所有使用@OpenApiService注解的类Map<String, Object> openApiServiceBeanMap = ApplicationContextHelper.getBeansWithAnnotation(OpenApiService.class);if (null == openApiServiceBeanMap || openApiServiceBeanMap.isEmpty()) {LOGGER.info("open api service bean map is empty");return;}for (Map.Entry<String, Object> map : openApiServiceBeanMap.entrySet()) {//获取扫描类下所有方法Method[] methods = ReflectionUtils.getAllDeclaredMethods(map.getValue().getClass());for (Method method : methods) {atomicInteger.incrementAndGet();//找到带有OpenApi 注解的方法OpenApi openApi = AnnotationUtils.findAnnotation(method, OpenApi.class);if (null == openApi) {continue;}//获取业务参数对象String paramName = getParamName(method);if (StringUtils.isBlank(paramName)) {LOGGER.warn("Api接口业务参数缺失 >> method = {}", openApi.method());continue;}//组建ApiModel- 放入api容器apiContainer.put(openApi.method(), new ApiModel(map.getKey(), method, paramName));LOGGER.info("Api接口加载成功 >> method = {} , desc={}", openApi.method(), openApi.desc());}}LOGGER.info("Api接口容器加载完毕 >> size = {} loopTimes={}", apiContainer.size(), atomicInteger.get());}/*** 获取业务参数对象** @param method* @return*/private String getParamName(Method method) {ArrayList<String> result = new ArrayList<>();final Matcher matcher = PATTERN.matcher(method.toGenericString());if (matcher.find()) {int groupCount = matcher.groupCount() + 1;for (int i = 0; i < groupCount; i++) {result.add(matcher.group(i));}}//获取参数部分if (result.size() >= 6) {String[] params =StringUtils.splitByWholeSeparatorPreserveAllTokens(result.get(5), PARAMS_SEPARATOR);if (params.length >= 2) {return params[1];}}return null;}}

3.3.5API请求处理客户端

作用说明:统一网关controller中调用一下方法进行验签与执行相应的业务方法。

//导包 太长,这里省略了,详见源码
/*** Api请求客户端** @author 程序员小强*/
@Service
public class ApiClient {/*** 日志*/private static final Logger LOGGER = LoggerFactory.getLogger(ApiClient.class);/*** jackson 序列化工具类*/private static final ObjectMapper JSON_MAPPER = new ObjectMapper();/*** Api本地容器*/private final ApiContainer apiContainer;public ApiClient(ApiContainer apiContainer) {this.apiContainer = apiContainer;}@Resourceprivate ApplicationProperty applicationProperty;/*** 验签** @param params          请求参数* @param requestRandomId 请求随机标识(用于日志中分辨是否是同一次请求)* @param charset         请求编码* @param signType        签名格式* @author 程序员小强*/public void checkSign(Map<String, Object> params, String requestRandomId, String charset, String signType) {try {//校验签名开关if (!applicationProperty.getIsCheckSign()) {LOGGER.warn("【{}】>> 验签开关关闭", requestRandomId);return;}//map类型转换Map<String, String> map = new HashMap<>(params.size());for (String s : params.keySet()) {map.put(s, params.get(s).toString());}LOGGER.warn("【{}】 >> 验签参数 {}", requestRandomId, map);boolean checkSign = AlipaySignature.rsaCheckV1(map, applicationProperty.getPublicKey(), charset, signType);if (!checkSign) {LOGGER.info("【{}】 >> 验签失败 >> params = {}", requestRandomId, JSON.toJSONString(params));throw new BusinessException(ApiExceptionEnum.INVALID_SIGN);}LOGGER.warn("【{}】 >> 验签成功", requestRandomId);} catch (Exception e) {LOGGER.error("【{}】 >> 验签异常 >> params = {}, error = {}",requestRandomId, JSON.toJSONString(params), ExceptionUtils.getStackTrace(e));throw new BusinessException(ApiExceptionEnum.INVALID_SIGN);}}/*** Api调用方法** @param method          请求方法* @param requestRandomId 请求随机标识* @param content         请求体* @author 程序员小强*/public ResultModel invoke(String method, String requestRandomId, String content) throws Throwable {//获取api方法ApiModel apiModel = apiContainer.get(method);if (null == apiModel) {LOGGER.info("【{}】 >> API方法不存在 >> method = {}", requestRandomId, method);throw new BusinessException(ApiExceptionEnum.API_NOT_EXIST);}//获得spring beanObject bean = ApplicationContextHelper.getBean(apiModel.getBeanName());if (null == bean) {LOGGER.warn("【{}】 >> API方法不存在 >> method = {}, beanName = {}", requestRandomId, method, apiModel.getBeanName());throw new BusinessException(ApiExceptionEnum.API_NOT_EXIST);}//处理业务参数// 忽略JSON字符串中存在,而在Java中不存在的属性JSON_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);// 设置下划线序列化方式JSON_MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);Object result = JSON_MAPPER.readValue(content, Class.forName(apiModel.getParamName()));//校验参数ValidateUtils.validate(result);//执行对应方法try {Object obj = apiModel.getMethod().invoke(bean, requestRandomId, result);return ResultModel.success(obj);} catch (Exception e) {if (e instanceof InvocationTargetException) {throw ((InvocationTargetException) e).getTargetException();}throw new BusinessException(ApiExceptionEnum.SYSTEM_ERROR);}}
}

3.3.6启动类

/*** 统一网关平台-启动类** @author 程序员小强*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class})
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

3.3.7统一异常处理

/*** 统一异常处理** @author 程序员小强*/
@Slf4j
@ControllerAdvice
@EnableAspectJAutoProxy
public class ExceptionAdvice {/*** 日志*/private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionAdvice.class);@CrossOrigin@ResponseBody@ExceptionHandler(value = Exception.class)public ResultModel defaultExceptionHandler(Exception exception, HttpServletResponse response) {ResultModel result;try {LOGGER.warn("全局业务处理异常 >> error = {}", exception.getMessage(), exception);throw exception;} catch (BusinessException e) {result = ResultModel.error(e.getCode(), e.getMsg());} catch (HttpRequestMethodNotSupportedException e) {String errorMsg = MessageFormat.format(ApiExceptionEnum.INVALID_REQUEST_ERROR.getMsg(), e.getMethod(), e.getSupportedHttpMethods());result = ResultModel.error(ApiExceptionEnum.INVALID_REQUEST_ERROR.getCode(), errorMsg);} catch (MissingServletRequestParameterException e) {String errorMsg = MessageFormat.format(ApiExceptionEnum.INVALID_PUBLIC_PARAM.getMsg(), e.getMessage());result = ResultModel.error(ApiExceptionEnum.INVALID_PUBLIC_PARAM.getCode(), errorMsg);} catch (Exception e) {result = ResultModel.error(ApiExceptionEnum.SYSTEM_ERROR.getCode(), ApiExceptionEnum.SYSTEM_ERROR.getMsg());}return result;}
}

3.3.8统一网关controller

//导包 太长,这里省略了,详见源码/*** 统一网关*/
@RestController
@RequestMapping("/open")
public class OpenApiController {private static final Logger LOGGER = LoggerFactory.getLogger(OpenApiController.class);@Autowiredprivate ApiClient apiClient;/*** 统一网关入口** @param method       请求方法* @param version      版本* @param apiRequestId 请求标识(用于日志中分辨是否是同一次请求)* @param charset      请求编码* @param signType     签名格式* @param sign         签名* @param content      业务内容参数* @author 程序员小强*/@PostMapping("/gateway")public ResultModel gateway(@RequestParam(value = "app_id", required = true) String appId,@RequestParam(value = "method", required = true) String method,@RequestParam(value = "version", required = true) String version,@RequestParam(value = "api_request_id", required = true) String apiRequestId,@RequestParam(value = "charset", required = true) String charset,@RequestParam(value = "sign_type", required = true) String signType,@RequestParam(value = "sign", required = true) String sign,@RequestParam(value = "content", required = true) String content,HttpServletRequest request) throws Throwable {Map<String, Object> params = WebUtils.getParametersStartingWith(request, StringPool.EMPTY);LOGGER.info("【{}】>> 网关执行开始 >> method={} params = {}", apiRequestId, method, JSON.toJSONString(params));long start = SystemClock.millisClock().now();//验签apiClient.checkSign(params, apiRequestId, charset, signType);//请求接口ResultModel result = apiClient.invoke(method, apiRequestId, content);LOGGER.info("【{}】>> 网关执行结束 >> method={},result = {}, times = {} ms",apiRequestId, method, JSON.toJSONString(result), (SystemClock.millisClock().now() - start));return result;}}

3.3.9测试实战DEMO

入参BO

/*** 使用注解做参数校验*/
public class Test1BO implements Serializable {private static final long serialVersionUID = -1L;@Valid@NotEmpty(message = "集合不为空!")@Size(min = 1, message = "最小为{min}")private List<Item> itemList;//省略 get/set/*** 内部类*/public static class Item {@NotBlank(message = "username 不能为空!")private String username;@NotBlank(message = "password 不能为空!")private String password;@NotBlank(message = "realName 不能为空!")private String realName;//省略 get/set}
}

测试service接口
注意:注解@OpenApi 使用 ,method就是入参中的方法

/*** 测试开放接口1*/
public interface TestOneService {/*** 方法1*/@OpenApi(method = "open.api.test.one.method1", desc = "测试接口1,方法1")void testMethod1(String requestId, Test1BO test1BO);
}

测试接口实现类

/*** 测试开放接口1* <p>* 注解@OpenApiService > 开放接口自定义注解,用于启动时扫描接口*/
@Service
@OpenApiService
public class TestOneServiceImpl implements TestOneService {/*** 日志*/private static final Logger LOGGER = LoggerFactory.getLogger(TestOneServiceImpl.class);/*** 方法1*/@Overridepublic void testMethod1(String requestId, Test1BO test1BO) {LOGGER.info("【{}】>> 测试开放接口1 >> 方法1 params={}", requestId, JSON.toJSONString(test1BO));}
}

3.4项目启动与测试

访问地址:http://localhost:8821/open/gateway

3.4.1不验签连通性测试

注:为了方便调试先将配置文件中的验签开关修改为 false

open.api.common.key.isCheckSign=false

正常访问

异常情况

3.4.2验签测试

将配置文件验签开关开启

open.api.common.key.isCheckSign=true

由于签名是根据私钥+动态的参数根据规则生成的,在PostMan上测试还得先生成签名,在源码文件中有一个测试Demo如下
带签名正常访问
**
签名异常情况下访问

4.总结与注意事项

  • 通过以上方式实现的统一网关,在后续添加业务的时候,只需要添加对应的service,并加好相应注解即可。

  • 目前配置的参数入参默认是下划线方式,如果业务中要用驼峰,可以改以下方法。

    • com.open.api.config.gateway.ApiClient#invoke
    • 目前配置的是,默认下划线方式,参考了大厂的开放接口,大部分都是下划线方式的参数
  • 需要自定义业务接口请参考 3.3.9测试实战DEMO。

因为使用反射方式调用方法,所以自定义接口的时候也需要遵循规范,参数为2个
参数1 String requestId,
参数2 业务类型对象参数,实际是 网关入口,content 内容JSON转换后的对象参数。

5.源码地址

源码地址:传送门

关注程序员小强公众号更多编程趣事,知识心得与您分享

Java实现系统统一对外开放网关入口设计相关推荐

  1. java风控系统规则引擎_如何设计一套规则引擎系统

    很早之前就想写一篇关于「规则引擎」的文章,但是一直苦于没有时间.刚好最近给团队小伙伴梳理了我设计的引擎的使用和原理,正好借此机会在此写下我们的心得. 「规则引擎」系统一般而言,在风控中使用较多,但是经 ...

  2. java竞拍系统代码,网上拍卖系统的设计与实现(源代码及全套资料).doc

    分类号:TP317 U D C:D10621-408-(2007) 6037-0 密 级:公 开 编 号:2003214017 XX信息工程学院 学位论文 网上拍卖系统 论文作者姓名:XX申请学位专业 ...

  3. 基于JAVA服务器监控系统设计,远程数字图像监控系统中web服务器的设计与实现...

    摘要: 数字图像监控系统在目前处于小规模发展阶段,系统的核心是监控服务器.在一些小规模的应用中,监控服务器同时还充当着监控终端的角色.但是,大部分监控系统都没有考虑用户权限.安全性.收费模式等方面的问 ...

  4. JAVA高级应用课程设计(网上书城系统——会员登陆模块的设计与实现)

    课程设计报告 课   程  名   称: JAVA高级应用课程设计 设   计  题   目:网上书城系统--会员登陆模块的设计与实现 目 录 一.开发背景. 1 (一)背景概述. 1 (二)发展前景 ...

  5. java毕业设计颜如玉图书销售网站的设计与实现Mybatis+系统+数据库+调试部署

    java毕业设计颜如玉图书销售网站的设计与实现Mybatis+系统+数据库+调试部署 java毕业设计颜如玉图书销售网站的设计与实现Mybatis+系统+数据库+调试部署 本源码技术栈: 项目架构:B ...

  6. java计算机毕业设计线上投保的设计源码+系统+数据库+lw文档+mybatis+运行部署

    java计算机毕业设计线上投保的设计源码+系统+数据库+lw文档+mybatis+运行部署 java计算机毕业设计线上投保的设计源码+系统+数据库+lw文档+mybatis+运行部署 本源码技术栈: ...

  7. java毕业设计——基于java+Socket+sqlserver的远程监控系统软件设计与实现(毕业论文+程序源码)——远程监控系统

    基于java+Socket+sqlserver的远程监控系统软件设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+Socket+sqlserver的远程监控系统软件设计与实现,文章 ...

  8. java修改数据库表结构_数据库设计(一):设计传统系统表结构(Java开发)

    以下文章来源于微信公众号 程序编程之旅 ,作者陈浩翔 此处仅供本人学习记录之用,侵删 本篇为第一篇.讲解传统系统的表结构设计(Java开发). 讲讲如何避免数据库设计的一些坑,方便后期的开发与维护. ...

  9. Java消息系统简单设计与实现

    前言:由于导师在我的毕设项目里加了消息系统(本来想水水就过的..),没办法...来稍微研究研究吧..简单简单... 需求分析 我的毕设是一个博客系统,类似于简书这样的,所以消息系统也类似,在用户的消息 ...

  10. java建站系统开发教程系列之设计表结构

    java建站系统开发教程系列之设计表结构 根据需求设计表结构如下: SET FOREIGN_KEY_CHECKS=0; -- Table structure for tbl_articles -- - ...

最新文章

  1. 自己动手修改龙邱信标灯固件FM频率
  2. 20141016--for 菱形
  3. 工业机器人行业研究报告
  4. 为什么很多招聘信息都要求三年以上的工作经验?
  5. 漫画科普:天线的原理?
  6. lr模型和dnn模型_建立ML或DNN模型的技巧
  7. html中单选框颜色怎么改,如何更改单选按钮的颜色?
  8. sudo配置临时取得root权限
  9. java 拦截器ajax_(转)拦截器深入实践 - JAVA XML JAVASCRIPT AJAX CSS - BlogJava
  10. 以小见大:如何设计注册登录页?
  11. 1. 根据输出的数据,对各个阶维度的反推+2.tf中生成根据指定的shape,tensor的各个阶的维度判断
  12. 【Flink】Flink SQL 报错 ClassCastException: VarCharType cannot be cast to RowType
  13. 计算机视觉技术测试物体距离,应用计算机视觉技术检测物体的形变
  14. igmp是哪个层协议_【干货】IGMPv1协议闲聊
  15. nginx-status详解
  16. 创建crawlspider爬虫 学习笔记
  17. centos7下yum源安装saltstack
  18. 一篇文章学懂ADB命令和Monkey命令
  19. matlab 拟合函数 调用,matlab拟合函数
  20. 全球顶级的14位程序员!

热门文章

  1. Oracle树形结构拖拽之插队重新排序
  2. xshell 连接vbox 虚拟机
  3. navicat 导入dmp文件
  4. Java|如何用Java定义一个类
  5. 如何辨别BGP带宽的真假?
  6. 计算机加密win7,Win7系统如何加密电脑硬盘?Win7系统全盘加密方法介绍
  7. 离散数学3_第1章__一些重要的重言蕴涵式__推理定律
  8. 闲的无聊写了个很(wu)有(liao)意(dao)思(bao)的程序
  9. 2021最全财富密码,95个即将发币项目概览
  10. iOS企业ipa(299)证书制作、打包发布全流程