Pre Merge pull request !7 from nocodery/master
commit
1ddb34b028
@ -1,23 +1,48 @@ |
||||
package com.gitee.sop.gateway.config; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.apache.commons.lang.StringUtils; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.data.redis.core.StringRedisTemplate; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.PostConstruct; |
||||
|
||||
@Slf4j |
||||
@Component |
||||
public class MyConfig { |
||||
|
||||
@Autowired |
||||
private StringRedisTemplate redisTemplate; |
||||
private static String APP_AUTH_TOKEN_PREFIX = "com.gitee.sop.oauth2_access_token:"; |
||||
|
||||
public static String getAccessTokenKey(String accessToken) { |
||||
return APP_AUTH_TOKEN_PREFIX + accessToken; |
||||
} |
||||
|
||||
|
||||
@PostConstruct |
||||
public void after() { |
||||
ApiConfig.getInstance().setTokenValidator(apiParam -> { |
||||
// 获取客户端传递过来的token
|
||||
String token = apiParam.fetchAccessToken(); |
||||
return !StringUtils.isBlank(token); |
||||
// TODO: 校验token有效性,可以从redis中读取
|
||||
try { |
||||
String token = apiParam.fetchAccessToken(); |
||||
if (StringUtils.isBlank(token)) { |
||||
return Boolean.FALSE; |
||||
} |
||||
String json = redisTemplate.opsForValue().get(getAccessTokenKey(token)); |
||||
if(StringUtils.isEmpty(json)) { |
||||
return Boolean.FALSE; |
||||
} |
||||
|
||||
// 返回true表示这个token真实、有效
|
||||
JSONObject userInfo = JSONObject.parseObject(json); |
||||
return userInfo != null; |
||||
} catch (Exception e) { |
||||
log.error("redisError", e); |
||||
return Boolean.FALSE; |
||||
} |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,132 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" 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"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<parent> |
||||
<groupId>com.gitee.sop</groupId> |
||||
<artifactId>sop-parent</artifactId> |
||||
<version>4.1.0-SNAPSHOT</version> |
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> |
||||
</parent> |
||||
<groupId>com.gitee.sop.openapi</groupId> |
||||
<artifactId>sop-openapi</artifactId> |
||||
<name>sop-openapi</name> |
||||
|
||||
<properties> |
||||
<apollo.version>1.1.0</apollo.version> |
||||
</properties> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>com.gitee.sop</groupId> |
||||
<artifactId>sop-service-common</artifactId> |
||||
<version>4.1.0-SNAPSHOT</version> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-web</artifactId> |
||||
</dependency> |
||||
|
||||
<!-- 使用nacos注册中心 --> |
||||
<dependency> |
||||
<groupId>com.alibaba.cloud</groupId> |
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework.cloud</groupId> |
||||
<artifactId>spring-cloud-starter-openfeign</artifactId> |
||||
</dependency> |
||||
|
||||
<!-- swagger2 --> |
||||
<dependency> |
||||
<groupId>io.springfox</groupId> |
||||
<artifactId>springfox-swagger2</artifactId> |
||||
<version>2.9.2</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>com.github.xiaoymin</groupId> |
||||
<artifactId>swagger-bootstrap-ui</artifactId> |
||||
<version>1.9.5</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>io.springfox</groupId> |
||||
<artifactId>springfox-swagger-ui</artifactId> |
||||
<version>2.9.2</version> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-test</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
|
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-data-redis</artifactId> |
||||
</dependency> |
||||
<!-- redis 依赖commons-pool --> |
||||
<dependency> |
||||
<groupId>org.apache.commons</groupId> |
||||
<artifactId>commons-lang3</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>mysql</groupId> |
||||
<artifactId>mysql-connector-java</artifactId> |
||||
<scope>runtime</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>net.oschina.durcframework</groupId> |
||||
<artifactId>fastmybatis-spring-boot-starter</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.projectlombok</groupId> |
||||
<artifactId>lombok</artifactId> |
||||
<version>1.18.4</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-data-jpa</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>com.ctrip.framework.apollo</groupId> |
||||
<artifactId>apollo-client</artifactId> |
||||
<version>${apollo.version}</version> |
||||
</dependency> |
||||
</dependencies> |
||||
|
||||
<build> |
||||
<plugins> |
||||
<!-- 打包时跳过测试 --> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-surefire-plugin</artifactId> |
||||
<version>2.12.4</version> |
||||
<configuration> |
||||
<skipTests>true</skipTests> |
||||
</configuration> |
||||
</plugin> |
||||
<plugin> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
|
||||
<repositories> |
||||
<repository> |
||||
<id>spring-milestones</id> |
||||
<name>Spring Milestones</name> |
||||
<url>https://repo.spring.io/milestone</url> |
||||
</repository> |
||||
</repositories> |
||||
</project> |
@ -0,0 +1,14 @@ |
||||
package com.gitee.sop.openapi; |
||||
|
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; |
||||
|
||||
@EnableDiscoveryClient |
||||
@SpringBootApplication |
||||
public class SopOpenapiApplication { |
||||
|
||||
public static void main(String[] args) { |
||||
SpringApplication.run(SopOpenapiApplication.class, args); |
||||
} |
||||
} |
@ -0,0 +1,41 @@ |
||||
package com.gitee.sop.openapi.config; |
||||
|
||||
import com.ctrip.framework.apollo.Config; |
||||
import com.ctrip.framework.apollo.ConfigService; |
||||
import com.ctrip.framework.apollo.model.ConfigChange; |
||||
import com.ctrip.framework.apollo.model.ConfigChangeEvent; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.config.BeanPostProcessor; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.PostConstruct; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/5 16:06 |
||||
*/ |
||||
@Slf4j |
||||
@Component |
||||
public class ApolloApplicationConfig implements BeanPostProcessor { |
||||
|
||||
|
||||
@PostConstruct |
||||
public void init() { |
||||
loadApplication(); |
||||
} |
||||
|
||||
private void loadApplication() { |
||||
Config config = ConfigService.getAppConfig(); |
||||
config.addChangeListener(this::getChangedEvent); |
||||
} |
||||
|
||||
|
||||
private void getChangedEvent(ConfigChangeEvent changeEvent) { |
||||
for (String key : changeEvent.changedKeys()) { |
||||
ConfigChange change = changeEvent.getChange(key); |
||||
log.info("Found change - key: {}, oldValue: {}, newValue: {}, changeType:{}", |
||||
change.getPropertyName(), change.getOldValue(), |
||||
change.getNewValue(), change.getChangeType()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
package com.gitee.sop.openapi.config; |
||||
|
||||
import com.gitee.sop.servercommon.bean.ServiceConfig; |
||||
import com.gitee.sop.servercommon.swagger.SwaggerSupport; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2; |
||||
|
||||
@Configuration |
||||
public class OpenapiConfig { |
||||
|
||||
static { |
||||
ServiceConfig.getInstance().getI18nModules().add("i18n/isp/auth_error"); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 开启文档,本地微服务文档地址:http://localhost:2222/doc.html
|
||||
* http://ip:port/v2/api-docs
|
||||
*/ |
||||
@Configuration |
||||
@EnableSwagger2 |
||||
public static class Swagger2 extends SwaggerSupport { |
||||
@Override |
||||
protected String getDocTitle() { |
||||
return "开放平台API"; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean swaggerAccessProtected() { |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
package com.gitee.sop.openapi.config; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect; |
||||
import com.fasterxml.jackson.annotation.PropertyAccessor; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
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.serializer.Jackson2JsonRedisSerializer; |
||||
import org.springframework.data.redis.serializer.StringRedisSerializer; |
||||
|
||||
@Configuration |
||||
public class RedisConfig { |
||||
|
||||
|
||||
@Bean |
||||
public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory) { |
||||
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>(); |
||||
redisTemplate.setConnectionFactory(redisConnectionFactory); |
||||
|
||||
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); |
||||
ObjectMapper objectMapper = new ObjectMapper(); |
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); |
||||
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); |
||||
jackson2JsonRedisSerializer.setObjectMapper(objectMapper); |
||||
|
||||
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); |
||||
|
||||
// key采用String的序列化方式
|
||||
redisTemplate.setKeySerializer(stringRedisSerializer); |
||||
// hash的key也采用String的序列化方式
|
||||
redisTemplate.setHashKeySerializer(stringRedisSerializer); |
||||
// value采用jackson序列化方式
|
||||
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); |
||||
// hash的value采用jackson序列化方式
|
||||
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); |
||||
redisTemplate.afterPropertiesSet(); |
||||
return redisTemplate; |
||||
} |
||||
} |
@ -0,0 +1,80 @@ |
||||
package com.gitee.sop.openapi.config; |
||||
|
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.data.redis.core.RedisTemplate; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/2 17:22 |
||||
*/ |
||||
@Slf4j |
||||
@Component |
||||
public class RedisOperator { |
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate; |
||||
|
||||
public RedisOperator(final RedisTemplate<String, Object> redisTemplate) { |
||||
this.redisTemplate = redisTemplate; |
||||
} |
||||
|
||||
public boolean expire(String key, long time) { |
||||
try { |
||||
if (time >= 0) { |
||||
redisTemplate.expire(key, time, TimeUnit.SECONDS); |
||||
} |
||||
return true; |
||||
} catch (Exception e) { |
||||
log.error("redisTemplate.expire.Error", e); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
|
||||
public long getExpire(String key) { |
||||
return redisTemplate.getExpire(key, TimeUnit.SECONDS); |
||||
} |
||||
|
||||
public Object get(String key) { |
||||
return key == null ? null : redisTemplate.opsForValue().get(key); |
||||
} |
||||
|
||||
public boolean set(String key, Object value) { |
||||
try { |
||||
redisTemplate.opsForValue().set(key, value); |
||||
return true; |
||||
} catch (Exception e) { |
||||
log.error("redisTemplate.set.Error", e); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 普通缓存放入并设置时间 |
||||
* |
||||
* @param key 键 |
||||
* @param value 值 |
||||
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 |
||||
* @return true成功 false 失败 |
||||
*/ |
||||
public boolean set(String key, Object value, long time) { |
||||
try { |
||||
if (time > 0) { |
||||
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); |
||||
} else { |
||||
set(key, value); |
||||
} |
||||
return true; |
||||
} catch (Exception e) { |
||||
log.error("redisTemplate.setAndExpire.Error", e); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
|
||||
public boolean del(String key) { |
||||
return redisTemplate.delete(key); |
||||
} |
||||
} |
@ -0,0 +1,68 @@ |
||||
package com.gitee.sop.openapi.controller; |
||||
|
||||
import com.gitee.sop.openapi.config.RedisOperator; |
||||
import com.gitee.sop.openapi.po.AuthCodePo; |
||||
import com.gitee.sop.openapi.vo.AuthCodeVo; |
||||
import com.gitee.sop.openapi.service.AuthCodeService; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import javax.validation.constraints.NotBlank; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
@Slf4j |
||||
@RestController |
||||
@Api(tags = "超浏授权接口") |
||||
public class AuthCodeController { |
||||
|
||||
|
||||
private final AuthCodeService authCodeService; |
||||
private final RedisOperator redisOperator; |
||||
|
||||
|
||||
@Value("${test.redis}") |
||||
private String testRedis; |
||||
|
||||
public void setTestRedis(String testRedis) { |
||||
this.testRedis = testRedis; |
||||
} |
||||
|
||||
public String getTestRedis() { |
||||
return testRedis; |
||||
} |
||||
|
||||
public AuthCodeController(final AuthCodeService authCodeService, final RedisOperator redisOperator) { |
||||
this.authCodeService = authCodeService; |
||||
this.redisOperator = redisOperator; |
||||
} |
||||
|
||||
|
||||
@ApiOperation(value = "获取code") |
||||
@PostMapping("/auth/code") |
||||
public AuthCodeVo getCode(@RequestBody AuthCodePo authCodePo) { |
||||
return authCodeService.getCode(authCodePo); |
||||
} |
||||
|
||||
|
||||
@ApiOperation(value = "解除授权") |
||||
@PostMapping("/auth/cancel") |
||||
public void cancelToken(@NotBlank(message = "{isp.auth.user_id_empty_error}") Long userId) { |
||||
authCodeService.cancelToken(userId); |
||||
} |
||||
|
||||
|
||||
@ApiOperation(value = "测试接口") |
||||
@GetMapping("/test") |
||||
public Map<String, Object> test() { |
||||
Map<String, Object> map = new HashMap<>(); |
||||
map.put("code", 0); |
||||
map.put("msg", "success"); |
||||
redisOperator.set("test11", testRedis); |
||||
return map; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,27 @@ |
||||
package com.gitee.sop.openapi.controller; |
||||
|
||||
import com.gitee.sop.openapi.po.AuthTokePo; |
||||
import com.gitee.sop.servercommon.annotation.Open; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestBody; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
@Slf4j |
||||
@RestController |
||||
@Api(tags = "插件应用获取token接口") |
||||
public class AuthTokenController { |
||||
|
||||
|
||||
|
||||
@Open(value = "auth.get.token", ignoreValidate = true, mergeResult = false, needToken = false) |
||||
@PostMapping(value = "/token") |
||||
@ApiOperation(value = "code换token", notes = "插件服务器获取token") |
||||
public String getToken(@RequestBody AuthTokePo tokePo) { |
||||
|
||||
|
||||
return ""; |
||||
} |
||||
} |
@ -0,0 +1,61 @@ |
||||
package com.gitee.sop.openapi.entity; |
||||
|
||||
import com.google.common.base.MoreObjects; |
||||
import lombok.Data; |
||||
|
||||
import javax.persistence.*; |
||||
import java.util.Date; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/4 18:59 |
||||
*/ |
||||
@Data |
||||
@MappedSuperclass |
||||
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) |
||||
public abstract class BaseEntity { |
||||
|
||||
@Id |
||||
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||
@Column(name = "id") |
||||
private Long id; |
||||
|
||||
@Column(name = "gmt_create", nullable = false) |
||||
private Date gmtCreate; |
||||
|
||||
@Column(name = "gmt_modified") |
||||
private Date gmtModified; |
||||
|
||||
@PrePersist |
||||
protected void prePersist() { |
||||
if (this.gmtCreate == null) { |
||||
gmtCreate = new Date(); |
||||
} |
||||
if (this.gmtModified == null) { |
||||
gmtModified = new Date(); |
||||
} |
||||
} |
||||
|
||||
@PreUpdate |
||||
protected void preUpdate() { |
||||
this.gmtModified = new Date(); |
||||
} |
||||
|
||||
@PreRemove |
||||
protected void preRemove() { |
||||
this.gmtModified = new Date(); |
||||
} |
||||
|
||||
protected MoreObjects.ToStringHelper toStringHelper() { |
||||
return MoreObjects.toStringHelper(this) |
||||
.omitNullValues() |
||||
.add("id", id) |
||||
.add("gmtCreate", gmtCreate) |
||||
.add("gmtModified", gmtModified); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return toStringHelper().toString(); |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
package com.gitee.sop.openapi.entity; |
||||
|
||||
import lombok.Data; |
||||
|
||||
import javax.persistence.Column; |
||||
import javax.persistence.Entity; |
||||
import javax.persistence.Table; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/2 16:57 |
||||
*/ |
||||
|
||||
@Data |
||||
@Entity |
||||
@Table(name = "isv_auth_token") |
||||
public class IsvAuthTokenEntity extends BaseEntity { |
||||
|
||||
/** appKey, 数据库字段:app_key */ |
||||
@Column(name = "app_key") |
||||
private String appKey; |
||||
|
||||
/** userId, 数据库字段:user_id */ |
||||
@Column(name = "user_id") |
||||
private Long userId; |
||||
|
||||
/** 1启用,2禁用, 数据库字段:status */ |
||||
@Column(name = "status", columnDefinition = "Bit default '1'") |
||||
private Boolean status = true; |
||||
|
||||
/** appAuthCode 数据库字段:app_auth_code */ |
||||
@Column(name = "app_auth_code") |
||||
private String appAuthCode; |
||||
|
||||
/** appAuthToken 数据库字段:app_auth_token */ |
||||
@Column(name = "app_auth_token") |
||||
private String appAuthToken; |
||||
|
||||
/** appRefreshToken 数据库字段:app_refresh_token */ |
||||
@Column(name = "app_refresh_token") |
||||
private String appRefreshToken; |
||||
} |
@ -0,0 +1,33 @@ |
||||
package com.gitee.sop.openapi.entity; |
||||
|
||||
import lombok.Data; |
||||
|
||||
import javax.persistence.Column; |
||||
import javax.persistence.Entity; |
||||
import javax.persistence.Table; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/2 16:22 |
||||
*/ |
||||
@Data |
||||
@Entity |
||||
@Table(name = "isv_info") |
||||
public class IsvInfoEntity extends BaseEntity { |
||||
|
||||
/** appKey, 数据库字段:app_key */ |
||||
@Column(name = "app_key") |
||||
private String appKey; |
||||
|
||||
/** 1启用,2禁用, 数据库字段:status */ |
||||
@Column(name = "status") |
||||
private Byte status; |
||||
|
||||
/** 1:RSA2,2:MD5 */ |
||||
@Column(name = "sign_type") |
||||
private Byte signType; |
||||
|
||||
@Column(name = "remark") |
||||
private String remark; |
||||
|
||||
} |
@ -0,0 +1,26 @@ |
||||
package com.gitee.sop.openapi.po; |
||||
|
||||
import com.gitee.sop.servercommon.param.validation.Group1; |
||||
import com.gitee.sop.servercommon.param.validation.Group2; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
import org.hibernate.validator.constraints.Length; |
||||
|
||||
import javax.validation.constraints.NotBlank; |
||||
import javax.validation.constraints.NotNull; |
||||
|
||||
|
||||
@Data |
||||
public class AuthCodePo { |
||||
|
||||
|
||||
@ApiModelProperty(value = "应用ID", required = true, example = "test1", position = 1) |
||||
@NotBlank(message = "{isp.auth.app_id_empty_error}", groups = Group1.class) |
||||
@Length(min = 1, max = 64, message = "{isp.auth.app_id_length_error}=1,64", groups = Group2.class) |
||||
private String appId; |
||||
|
||||
@ApiModelProperty(value = "用户ID", required = true, example = "1", position = 2) |
||||
@NotNull(message = "{isp.auth.user_id_empty_error}") |
||||
private Long userId; |
||||
|
||||
} |
@ -0,0 +1,45 @@ |
||||
package com.gitee.sop.openapi.po; |
||||
|
||||
import com.gitee.sop.servercommon.param.validation.Group1; |
||||
import com.gitee.sop.servercommon.param.validation.Group2; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
import org.hibernate.validator.constraints.Length; |
||||
|
||||
import javax.validation.constraints.NotBlank; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/2 18:31 |
||||
*/ |
||||
|
||||
@Data |
||||
public class AuthTokePo { |
||||
|
||||
|
||||
|
||||
/** |
||||
* 授权码.与refresh_token二选一,用户对应用授权后得到,即第一步中开发者获取到的app_auth_code值 |
||||
*/ |
||||
@ApiModelProperty(value = "应用ID", example = "test1", position = 1) |
||||
@NotBlank(message = "{isp.auth.app_id_empty_error}", groups = Group1.class) |
||||
@Length(min = 1, max = 64, message = "{isp.auth.app_id_length_error}=1,64", groups = Group2.class) |
||||
private String code; |
||||
|
||||
/** |
||||
* 授权类型。<br> |
||||
* 如果使用app_auth_code换取token,则为authorization_code, |
||||
* 如果使用refresh_token换取新的token,则为refresh_token |
||||
*/ |
||||
@NotBlank(message = "{isp.auth.user_token_empty_error}", groups = Group1.class) |
||||
@Length(min = 1, max = 64, message = "{isp.auth.user_token_length_error}=1,64", groups = Group2.class) |
||||
@ApiModelProperty(value = "授权类型不能为空", required = true, example = "1111", position = 2) |
||||
private String grantType; |
||||
|
||||
|
||||
/** |
||||
* 刷新令牌.与code二选一,可为空,刷新令牌时使用 |
||||
*/ |
||||
private String refreshToken; |
||||
|
||||
} |
@ -0,0 +1,17 @@ |
||||
package com.gitee.sop.openapi.repository; |
||||
|
||||
import com.gitee.sop.openapi.entity.IsvAuthTokenEntity; |
||||
import org.springframework.data.repository.PagingAndSortingRepository; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/4 19:12 |
||||
*/ |
||||
public interface IsvAuthTokenRepository extends PagingAndSortingRepository<IsvAuthTokenEntity, Long> { |
||||
|
||||
IsvAuthTokenEntity findFirstByAppKeyAndUserIdAndStatusTrue(String appKey, Long userId); |
||||
|
||||
IsvAuthTokenEntity findFirstByUserIdAndStatusTrue(Long userId); |
||||
|
||||
IsvAuthTokenEntity findFirstByAppAuthCode(String appAuthCode); |
||||
} |
@ -0,0 +1,14 @@ |
||||
package com.gitee.sop.openapi.repository; |
||||
|
||||
import com.gitee.sop.openapi.entity.IsvInfoEntity; |
||||
import org.springframework.data.repository.PagingAndSortingRepository; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/4 19:11 |
||||
*/ |
||||
public interface IsvInfoRepository extends PagingAndSortingRepository<IsvInfoEntity, Long> { |
||||
|
||||
IsvInfoEntity findFirstByAppKey(String appKey); |
||||
|
||||
} |
@ -0,0 +1,106 @@ |
||||
package com.gitee.sop.openapi.service; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.gitee.fastmybatis.core.query.Query; |
||||
import com.gitee.sop.openapi.config.RedisOperator; |
||||
import com.gitee.sop.openapi.entity.IsvAuthTokenEntity; |
||||
import com.gitee.sop.openapi.entity.IsvInfoEntity; |
||||
import com.gitee.sop.openapi.po.AuthCodePo; |
||||
import com.gitee.sop.openapi.repository.IsvAuthTokenRepository; |
||||
import com.gitee.sop.openapi.repository.IsvInfoRepository; |
||||
import com.gitee.sop.openapi.vo.AuthCodeVo; |
||||
import com.gitee.sop.openapi.util.MD5GeneratorUtil; |
||||
import com.gitee.sop.openapi.util.RedisKeyUtil; |
||||
import com.gitee.sop.servercommon.exception.ServiceException; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/2 16:28 |
||||
*/ |
||||
@Slf4j |
||||
@Service |
||||
public class AuthCodeService implements AuthService { |
||||
|
||||
private final IsvInfoRepository isvInfoRepository; |
||||
private final IsvAuthTokenRepository isvAuthTokenRepository; |
||||
private final RedisOperator redisOperator; |
||||
|
||||
public AuthCodeService(final IsvInfoRepository isvInfoRepository, |
||||
final IsvAuthTokenRepository isvAuthTokenRepository, |
||||
final RedisOperator redisOperator) { |
||||
this.isvInfoRepository = isvInfoRepository; |
||||
this.isvAuthTokenRepository = isvAuthTokenRepository; |
||||
this.redisOperator = redisOperator; |
||||
} |
||||
|
||||
|
||||
@Transactional |
||||
public AuthCodeVo getCode(AuthCodePo authCodePo) { |
||||
|
||||
IsvInfoEntity isvInfo = isvInfoRepository.findFirstByAppKey(authCodePo.getAppId()); |
||||
if (isvInfo == null || isvInfo.getStatus().intValue() == FORBIDDEN) { |
||||
log.error("appId已禁用:{}", authCodePo.getAppId()); |
||||
throw new ServiceException("应用ID未申请或已被禁用"); |
||||
} |
||||
|
||||
String authCode = MD5GeneratorUtil.generateValue(); |
||||
|
||||
IsvAuthTokenEntity authTokenEntity = isvAuthTokenRepository.findFirstByAppKeyAndUserIdAndStatusTrue(authCodePo.getAppId(), authCodePo.getUserId()); |
||||
AuthCodeVo authCodeVo = new AuthCodeVo(); |
||||
authCodeVo.setCode(authCode); |
||||
if (authTokenEntity == null) { |
||||
authCodeVo.setState(UNAUTHORIZED); |
||||
authTokenEntity = new IsvAuthTokenEntity(); |
||||
authTokenEntity.setAppAuthCode(authCode); |
||||
authTokenEntity.setAppKey(authCodePo.getAppId()); |
||||
authTokenEntity.setUserId(authCodePo.getUserId()); |
||||
} |
||||
authTokenEntity.setAppAuthCode(authCode); |
||||
isvAuthTokenRepository.save(authTokenEntity); |
||||
|
||||
// 写入redis缓存
|
||||
try { |
||||
String codeKey = RedisKeyUtil.getCodeKey(authCode); |
||||
redisOperator.set(codeKey, JSONObject.toJSON(authCodePo), CODE_TIMEOUT); |
||||
} catch (Exception e) { |
||||
throw new ServiceException("获取code失败"); |
||||
} |
||||
|
||||
return authCodeVo; |
||||
} |
||||
|
||||
|
||||
@Transactional |
||||
public void cancelToken(Long userId) { |
||||
// todo check userToken and get userId
|
||||
|
||||
Query query = new Query() |
||||
.eq("user_id", userId) |
||||
.eq("status", ENABLE_TOKEN); |
||||
|
||||
IsvAuthTokenEntity authTokenEntity = isvAuthTokenRepository.findFirstByUserIdAndStatusTrue(userId); |
||||
if (authTokenEntity == null) { |
||||
throw new ServiceException("用户未授权插件应用"); |
||||
} |
||||
|
||||
try { |
||||
authTokenEntity.setStatus(true); |
||||
isvAuthTokenRepository.save(authTokenEntity); |
||||
} catch (Exception e) { |
||||
throw new ServiceException("解除授权失败,系统忙,请稍后!"); |
||||
} |
||||
|
||||
String tokenKey = RedisKeyUtil.getAuthTokenKey(authTokenEntity.getAppAuthToken()); |
||||
String refreshKey = RedisKeyUtil.getRefreshTokenKey(authTokenEntity.getAppRefreshToken()); |
||||
try { |
||||
redisOperator.del(tokenKey); |
||||
redisOperator.del(refreshKey); |
||||
} catch (Exception e) { |
||||
throw new ServiceException("解除授权失败,系统忙,请稍后!"); |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
package com.gitee.sop.openapi.service; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/4 15:41 |
||||
*/ |
||||
public interface AuthService { |
||||
|
||||
int FORBIDDEN = 2; |
||||
|
||||
/** 0 插件应用未授权 **/ |
||||
int UNAUTHORIZED = 0; |
||||
/** 1 插件应用已授权 **/ |
||||
int AUTHORIZED = 1; |
||||
|
||||
Byte ENABLE_TOKEN = 1; |
||||
Byte DISABLE_TOKEN = 0; |
||||
|
||||
Long CODE_TIMEOUT = 3600L; |
||||
} |
@ -0,0 +1,83 @@ |
||||
package com.gitee.sop.openapi.service; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.gitee.sop.openapi.config.RedisOperator; |
||||
import com.gitee.sop.openapi.entity.IsvAuthTokenEntity; |
||||
import com.gitee.sop.openapi.po.AuthCodePo; |
||||
import com.gitee.sop.openapi.po.AuthTokePo; |
||||
import com.gitee.sop.openapi.repository.IsvAuthTokenRepository; |
||||
import com.gitee.sop.openapi.util.GrantType; |
||||
import com.gitee.sop.openapi.util.MD5GeneratorUtil; |
||||
import com.gitee.sop.openapi.util.RedisKeyUtil; |
||||
import com.gitee.sop.openapi.vo.AuthTokenVo; |
||||
import com.gitee.sop.servercommon.exception.ServiceException; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/4 15:21 |
||||
*/ |
||||
@Service |
||||
public class AuthTokenService implements AuthService { |
||||
|
||||
private final RedisOperator redisOperator; |
||||
private final IsvAuthTokenRepository isvAuthTokenRepository; |
||||
|
||||
public AuthTokenService(final RedisOperator redisOperator, |
||||
final IsvAuthTokenRepository isvAuthTokenRepository) { |
||||
this.redisOperator = redisOperator; |
||||
this.isvAuthTokenRepository = isvAuthTokenRepository; |
||||
} |
||||
|
||||
|
||||
public AuthTokenVo getToken(AuthTokePo authTokePo) { |
||||
|
||||
AuthTokenVo tokenVo = new AuthTokenVo(); |
||||
String grantType = authTokePo.getGrantType(); |
||||
if (GrantType.AUTHORIZATION_CODE.name().equals(grantType)) { |
||||
String codeKey = RedisKeyUtil.getCodeKey(authTokePo.getCode()); |
||||
Object jsonObj = redisOperator.get(codeKey); |
||||
if (jsonObj == null) { |
||||
throw new ServiceException("Invalid request"); |
||||
} |
||||
|
||||
AuthCodePo codePo = JSONObject.parseObject(jsonObj.toString(), AuthCodePo.class); |
||||
if (codePo == null) { |
||||
throw new ServiceException("Can not found user by code"); |
||||
} |
||||
IsvAuthTokenEntity authTokenEntity = isvAuthTokenRepository.findFirstByAppAuthCode(authTokePo.getCode()); |
||||
if (authTokenEntity == null) { |
||||
throw new ServiceException("Can not found user by code"); |
||||
} |
||||
|
||||
|
||||
String authToken = MD5GeneratorUtil.generateValue(); |
||||
String refreshToken = MD5GeneratorUtil.generateValue(); |
||||
|
||||
|
||||
authTokenEntity.setStatus(true); |
||||
authTokenEntity.setAppAuthToken(authToken); |
||||
authTokenEntity.setAppRefreshToken(refreshToken); |
||||
isvAuthTokenRepository.save(authTokenEntity); |
||||
|
||||
String authTokenKey = RedisKeyUtil.getAuthTokenKey(authToken); |
||||
String refreshTokenKey = RedisKeyUtil.getRefreshTokenKey(refreshToken); |
||||
redisOperator.set(authTokenKey, codePo); |
||||
redisOperator.set(refreshTokenKey, codePo); |
||||
|
||||
tokenVo.setAppAuthToken(authToken); |
||||
tokenVo.setAppRefreshToken(refreshToken); |
||||
tokenVo.setUserId(codePo.getUserId()); |
||||
return tokenVo; |
||||
} else if (GrantType.REFRESH_TOKEN.name().equals(grantType)) { |
||||
|
||||
|
||||
return tokenVo; |
||||
} else { |
||||
throw new ServiceException("Invalid grant"); |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,10 @@ |
||||
package com.gitee.sop.openapi.util; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/2 16:42 |
||||
*/ |
||||
public enum AuthErrorEnum { |
||||
|
||||
|
||||
} |
@ -0,0 +1,23 @@ |
||||
package com.gitee.sop.openapi.util; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/4 15:25 |
||||
*/ |
||||
public enum GrantType { |
||||
|
||||
AUTHORIZATION_CODE("authorization_code"), |
||||
PASSWORD("password"), |
||||
REFRESH_TOKEN("refresh_token"), |
||||
CLIENT_CREDENTIALS("client_credentials"); |
||||
|
||||
private String grantType; |
||||
|
||||
private GrantType(String grantType) { |
||||
this.grantType = grantType; |
||||
} |
||||
|
||||
public String toString() { |
||||
return this.grantType; |
||||
} |
||||
} |
@ -0,0 +1,27 @@ |
||||
package com.gitee.sop.openapi.util; |
||||
|
||||
import org.slf4j.helpers.FormattingTuple; |
||||
import org.slf4j.helpers.MessageFormatter; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/2 18:14 |
||||
*/ |
||||
public class LogFormatUtil { |
||||
|
||||
/** |
||||
* append log with pattern |
||||
* |
||||
* @param appendLogPattern like "aaa {} bbb {} ccc" |
||||
* @param appendLogArguments like "111, true" |
||||
*/ |
||||
public static String log(String appendLogPattern, Object ... appendLogArguments) { |
||||
FormattingTuple ft = MessageFormatter.arrayFormat(appendLogPattern, appendLogArguments); |
||||
return ft.getMessage(); |
||||
} |
||||
|
||||
|
||||
public static void main(String[] args) { |
||||
System.out.println(log("My name is {}, from {}", "hanmeimei", "London")); |
||||
} |
||||
} |
@ -0,0 +1,53 @@ |
||||
package com.gitee.sop.openapi.util; |
||||
|
||||
import com.gitee.sop.servercommon.exception.ServiceException; |
||||
|
||||
import java.security.MessageDigest; |
||||
import java.util.UUID; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/2 16:47 |
||||
*/ |
||||
public class MD5GeneratorUtil { |
||||
|
||||
private static final char[] hexCode = "0123456789abcdef".toCharArray(); |
||||
|
||||
private MD5GeneratorUtil() { |
||||
} |
||||
|
||||
public static String generateValue() throws ServiceException { |
||||
return generateValue(UUID.randomUUID().toString()); |
||||
} |
||||
|
||||
public static String toHexString(byte[] data) { |
||||
if (data == null) { |
||||
return null; |
||||
} else { |
||||
StringBuilder r = new StringBuilder(data.length * 2); |
||||
byte[] arr$ = data; |
||||
int len$ = data.length; |
||||
|
||||
for (int i$ = 0; i$ < len$; ++i$) { |
||||
byte b = arr$[i$]; |
||||
r.append(hexCode[b >> 4 & 15]); |
||||
r.append(hexCode[b & 15]); |
||||
} |
||||
|
||||
return r.toString(); |
||||
} |
||||
} |
||||
|
||||
public static String generateValue(String param) throws ServiceException { |
||||
try { |
||||
MessageDigest algorithm = MessageDigest.getInstance("MD5"); |
||||
algorithm.reset(); |
||||
algorithm.update(param.getBytes()); |
||||
byte[] messageDigest = algorithm.digest(); |
||||
return toHexString(messageDigest); |
||||
} catch (Exception var4) { |
||||
throw new ServiceException("OAuth Token cannot be generated."); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,30 @@ |
||||
package com.gitee.sop.openapi.util; |
||||
|
||||
import com.google.common.base.Joiner; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/2 17:47 |
||||
*/ |
||||
public class RedisKeyUtil { |
||||
|
||||
public static String CODE_PREFIX = "sop:app_auth_code"; |
||||
public static String AUTH_TOKEN_PREFIX = "sop:app_auth_token"; |
||||
public static String REFRESH_TOKEN_PREFIX = "sop:app_refresh_token"; |
||||
|
||||
|
||||
private static final Joiner JOINER = Joiner.on(":"); |
||||
|
||||
|
||||
public static String getCodeKey(String code) { |
||||
return JOINER.join(CODE_PREFIX, code); |
||||
} |
||||
|
||||
public static String getAuthTokenKey(String token) { |
||||
return JOINER.join(AUTH_TOKEN_PREFIX, token); |
||||
} |
||||
|
||||
public static String getRefreshTokenKey(String refreshToken) { |
||||
return JOINER.join(REFRESH_TOKEN_PREFIX, refreshToken); |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
package com.gitee.sop.openapi.vo; |
||||
|
||||
import lombok.Data; |
||||
|
||||
@Data |
||||
public class AuthCodeVo { |
||||
private String code; |
||||
private Integer state; |
||||
} |
@ -0,0 +1,40 @@ |
||||
package com.gitee.sop.openapi.vo; |
||||
|
||||
import lombok.Data; |
||||
|
||||
/*** |
||||
* |
||||
* @author wangping created on 2020/11/4 15:22 |
||||
*/ |
||||
@Data |
||||
public class AuthTokenVo { |
||||
|
||||
/** |
||||
* 授权令牌 |
||||
*/ |
||||
private String appAuthToken; |
||||
|
||||
/** |
||||
* 用户id |
||||
*/ |
||||
private Long userId; |
||||
|
||||
|
||||
/** |
||||
* 令牌有效期. -1:永久有效 |
||||
*/ |
||||
private long expiresIn = -1; |
||||
|
||||
/** |
||||
* 刷新令牌有效期. -1:永久有效 |
||||
*/ |
||||
private long reExpiresIn = -1; |
||||
|
||||
|
||||
/** |
||||
* 刷新令牌时使用。 |
||||
* 刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token |
||||
*/ |
||||
private String appRefreshToken; |
||||
|
||||
} |
@ -0,0 +1,2 @@ |
||||
app.id=open-apis |
||||
jdkVersion=1.8 |
@ -0,0 +1,16 @@ |
||||
spring: |
||||
redis: |
||||
cluster: |
||||
nodes: |
||||
- 10.1.11.46:7000 |
||||
- 10.1.11.46:7001 |
||||
- 10.1.11.46:7002 |
||||
jedis: |
||||
pool: |
||||
max-active: 10 |
||||
max-wait: -1 |
||||
max-idle: 3 |
||||
min-idle: 1 |
||||
timeout: 60000 |
||||
password: |
||||
|
@ -0,0 +1,16 @@ |
||||
spring: |
||||
redis: |
||||
sentinel: |
||||
master: mymaster |
||||
nodes: |
||||
- 192.168.0.114:26379 |
||||
- 192.168.0.114:26380 |
||||
- 192.168.0.114:26381 |
||||
jedis: |
||||
pool: |
||||
max-active: 10 |
||||
max-wait: -1 |
||||
max-idle: 9 |
||||
min-idle: 0 |
||||
timeout: 60000 |
||||
password: |
@ -0,0 +1,13 @@ |
||||
spring: |
||||
redis: |
||||
host: 52.81.162.154 |
||||
port: 8095 |
||||
jedis: |
||||
pool: |
||||
max-active: 10 |
||||
max-wait: -1 |
||||
max-idle: 9 |
||||
min-idle: 1 |
||||
database: 0 |
||||
timeout: 60000 |
||||
password: zixun@2020! |
@ -0,0 +1,37 @@ |
||||
spring.profiles.active=standalone |
||||
|
||||
server.port=8089 |
||||
spring.application.name=open-apis |
||||
# 注册中心 |
||||
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 |
||||
|
||||
|
||||
## 数据库配置 |
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver |
||||
spring.datasource.url=jdbc:mysql://localhost:3306/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai |
||||
spring.datasource.username=root |
||||
spring.datasource.password=root |
||||
spring.datasource.hikari.connectionInitSql=set names utf8mb4 |
||||
|
||||
#连接池 |
||||
spring.datasource.hikari.pool-name=HikariCP |
||||
spring.datasource.hikari.max-lifetime=500000 |
||||
spring.datasource.hikari.minimum-idle=2 |
||||
spring.datasource.hikari.idle-timeout=60000 |
||||
spring.datasource.hikari.maximum-pool-size=10 |
||||
spring.datasource.hikari.connection-test-query=select 1 |
||||
|
||||
# Naming strategy |
||||
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl |
||||
spring.jpa.hibernate.globally_quoted_identifiers=true |
||||
spring.jpa.properties.hibernate.globally_quoted_identifiers=true |
||||
spring.jpa.show-sql=false |
||||
|
||||
|
||||
# Apollo |
||||
app.id=open-apis |
||||
apollo.bootstrap.enabled=true |
||||
apollo.bootstrap.namespaces=application |
||||
apollo.bootstrap.eagerLoad.enable=true |
||||
|
||||
test.redis=wangping |
@ -0,0 +1,4 @@ |
||||
isp.auth.user_id_empty_error=the user_id can NOT be null |
||||
|
||||
isp.auth.app_id_empty_error=the app_id can NOT be null |
||||
isp.auth.app_id_length_error=The app_id length must >= {0} and <= {1} |
@ -0,0 +1,9 @@ |
||||
# 用户登录凭证不能为空 |
||||
isp.auth.user_id_empty_error=\u7528\u6237\u0049\u0044\u4e0d\u80fd\u4e3a\u7a7a |
||||
# 用户登录凭证长度必须在{0}和{1}之间 |
||||
#isp.auth.user_token_length_error=\u7528\u6237\u767b\u5f55\u51ed\u8bc1\u957f\u5ea6\u5fc5\u987b\u5728{0}\u548c{1}\u4e4b\u95f4 |
||||
|
||||
# 应用ID不能为空 |
||||
isp.auth.app_id_empty_error=\u5e94\u7528\u0049\u0044\u4e0d\u80fd\u4e3a\u7a7a |
||||
# 应用ID长度必须在{0}和{1}之间 |
||||
isp.auth.app_id_length_error=\u5e94\u7528\u0049\u0044\u957f\u5ea6\u5fc5\u987b\u5728{0}\u548c{1}\u4e4b\u95f4 |
@ -0,0 +1,5 @@ |
||||
# 错误配置 |
||||
|
||||
# 系统配置 |
||||
isp.error_isv.common-error=The system is busy. |
||||
isp.error_isv.invalid-parameter=Invalid parameter, {0} |
@ -0,0 +1,9 @@ |
||||
# 错误配置 |
||||
|
||||
# 系统繁忙 |
||||
isp.error_isv.common-error=\u7cfb\u7edf\u7e41\u5fd9 |
||||
# 参数无效 |
||||
isp.error_isv.invalid-parameter=\u53c2\u6570\u65e0\u6548, {0} |
||||
|
||||
# ==== 参数配置 ==== |
||||
|
@ -0,0 +1,20 @@ |
||||
package com.gitee.sop; |
||||
|
||||
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 ); |
||||
} |
||||
} |
Loading…
Reference in new issue