小辣椒
用处:一个注解完成大量的 get、set 操作
idea 安装 Lombok 插件,推荐用 properties 标签来管理更加规范
前 3 行比较通用,所以一起先写了,1,2 那个指定 maven 的解析方式,能避免环境不一样的错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starters</artifactId> <version>2.1.3.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <lombok.version>1.18.4</lombok.version> </properties>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency>
|
实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;
@Data @NoArgsConstructor @AllArgsConstructor public class Client {
private int userAcc;
private String userPwd;
}
|
单元测试
tip:springboot 中进行单元测试,不是很规范情况直接写版本就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
|
在 test 文件夹–>java 文件中创建路径加类名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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 javax.annotation.Resource;
@Slf4j @RunWith(SpringRunner.class) @SpringBootTest public class TestWiring { @Resource private DeviceTopoService deviceTopoService;
@Test public void fun1() { DeviceTopoEntity info = deviceTopoService.getById(1); log.info(String.valueOf(info)); } }
|
Fastjson2
选用 2 的版本,更加的好用
1 2 3 4 5 6
| <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.7</version> </dependency>
|
前端:都采用 post 请求
1 2 3 4 5 6 7 8 9 10 11
| export const loginM = (datas) => { return request({ url: '/index/login', method: 'post', data: datas }) }
loginM({ userAcc: this.username, userPwd: this.password }).then((res) => {...})
|
后端
1 2 3 4 5 6 7 8 9 10
| import com.alibaba.fastjson2.JSONObject;
@PostMapping("/login") public ResultVO loginHome(@RequestBody JSONObject obj) { String userAcc = obj.getString("userAcc");
String userPwd = obj.getString("userPwd"); return indexService.login(userAcc, userPwd); }
|
后端的拦截器配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package com.nwa.until;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Autowired private CheckTokenInterceptor checkTokenInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(checkTokenInterceptor) .addPathPatterns("/home/**") .excludePathPatterns("/index/**") .excludePathPatterns("/home/getLogList") .excludePathPatterns("/swagger-ui/**") .excludePathPatterns("/swagger2.html") .excludePathPatterns("/doc.html"); }
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/static/"); registry.addResourceHandler("swagger-ui.html") .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("doc.html") .addResourceLocations("classpath:/META-INF/resources/"); }
}
|
SpringBoot 启动文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| __ __ ______ / | / | / \ $$ | __ __ _______ $$ | __ __ __ /$$$$$$ | $$ | / | / | / |$$ | / |/ | / | $$ |__$$ | $$ | $$ | $$ |/$$$$$$$/ $$ |_/$$/ $$ | $$ | $$ $$ | $$ | $$ | $$ |$$ | $$ $$< $$ | $$ | $$$$$$$$ | $$ |_____ $$ \__$$ |$$ \_____ $$$$$$ \ $$ \__$$ | $$ | $$ | $$ |$$ $$/ $$ |$$ | $$ |$$ $$ | $$ | $$ | $$$$$$$$/ $$$$$$/ $$$$$$$/ $$/ $$/ $$$$$$$ | $$/ $$/ / \__$$ | $$ $$/ $$$$$$/
██╗ ██╗ ██╗ ██████╗██╗ ██╗██╗ ██╗ █████╗ ██║ ██║ ██║██╔════╝██║ ██╔╝╚██╗ ██╔╝██╔══██╗ ██║ ██║ ██║██║ █████╔╝ ╚████╔╝ ███████║ ██║ ██║ ██║██║ ██╔═██╗ ╚██╔╝ ██╔══██║ ███████╗╚██████╔╝╚██████╗██║ ██╗ ██║ ██║ ██║ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
SpringBoot 分页器
依赖
1 2 3 4 5
| <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.0</version> </dependency>
|
yml
1 2 3
| spring: main: allow-circular-references: true
|
impl(有待优化,封装分页类)
1 2 3 4 5 6 7 8 9 10 11
| @Override public ResultVO getMyWeekList(String months, String id, int page, int limit) { PageHelper.startPage(page, limit); //一般传过来1,100条 List<Client> clientS = adHomeMapper.getMyWeekList(months, id); if (clientS != null) { PageInfo<Client> goodsTbPageInfo = new PageInfo<>(clientS, 3); int count = (int) goodsTbPageInfo.getTotal(); resultVO = new ResultVO(ResStatus.OK, "列表信息返回成功!", count, clientS); } return resultVO; }
|
前端部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <el-table :data=" tableData.slice((currentPage - 1) * pageSize, currentPage * pageSize) " .... >..... </el-table> <div> <div class="block" style="margin-top: 15px"> <el-pagination align="center" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[1, 3, 5]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="tableData.length"> </el-pagination> </div> </div>
方法中: handleSizeChange (val) { console.log(`每页${val}条`) this.currentPage = 1 this.pageSize = val }, handleClick (row) { console.log(row) this.$data.drawe = true this.$data.userObj = row }, handleCurrentChange (val) { console.log(`当前页:${val}`) this.currentPage = val },
data里return中 tableData: [], currentPage: 1, pageSize: 5, total: '',
发起请求 getMonthsList () { getMyWeekList({ months: this.findContent, id: '', page: 1, limit: 100 }).then((resp) => { this.$data.tableData = resp.data.data }) },
|
Token
依赖
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
|
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| package com.nwa.until; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.JWTVerifier;
import java.util.Date; import java.util.HashMap; import java.util.Map;
public class JWTUtils { private static final long EXPIRE_DATE=30*610*100000; private static final String TOKEN_SECRET = "LuckyNWA666";
public static String token (String username, String password){ String token = ""; try { Date date = new Date(System.currentTimeMillis()+EXPIRE_DATE); Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); Map<String,Object> header = new HashMap<>(); header.put("typ","JWT"); header.put("alg","HS256"); token = JWT.create() .withHeader(header) .withClaim("username",username) .withClaim("password",password).withExpiresAt(date) .sign(algorithm); }catch (Exception e){ e.printStackTrace(); return null; } return token; } public static boolean verify(String token){
try { Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(token); return true; }catch (Exception e){ e.printStackTrace(); return false; } }
}
|
拦截调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| package com.nwa.until;
import com.fasterxml.jackson.databind.ObjectMapper; import com.nwa.bean.ResultVO; import io.jsonwebtoken.*; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter;
@Component public class CheckTokenInterceptor implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String method = request.getMethod(); if ("OPTIONS".equalsIgnoreCase(method)) { return true; } String token = request.getHeader("token"); System.out.println("进入拦截器"); System.out.println("token的值是" + token); if (token == null) { ResultVO resultVO = new ResultVO(ResStatus.FAIL, "请先登录", null); doResponse(response, resultVO);
} else { try { boolean verify = JWTUtils.verify(token); return verify; } catch (ExpiredJwtException e) { ResultVO resultVO = new ResultVO(ResStatus.FAIL, "登录过期", null); doResponse(response, resultVO); } catch (UnsupportedJwtException e) { ResultVO resultVO = new ResultVO(ResStatus.FAIL, "token不合法", null); doResponse(response, resultVO); } catch (Exception e) { ResultVO resultVO = new ResultVO(ResStatus.FAIL, "请先登录", null); doResponse(response, resultVO); } } return false; }
private void doResponse(HttpServletResponse response, ResultVO resultVO) throws IOException { response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); String s = new ObjectMapper().writeValueAsString(resultVO); out.println(s); out.flush(); out.close(); } }
登录成功时候 String token = JWTUtils.token(acc, pwd); client.setToken(token);
|
前端 request 中带上请求头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 登录后记得保存; window.sessionStorage.setItem("token", data.token); 接下去每次访问接口携带; request.interceptors.request.use( (config) => { config.headers.token = window.sessionStorage.getItem("token");
return config; }, function (error) { return Promise.reject(error); } );
|
Md5
没有加盐可以被破解,需优化
1 2 3 4 5
| <!--md5依赖--> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency>
|
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.nwa.until;
import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException;
public class MD5Utils { public static String md5(String pwd) { try { MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(pwd.getBytes()); return new BigInteger(1, md5.digest()).toString(16); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } }
|
比较简单,等以后再完善
1 2 3
| String md5Pwd = MD5Utils.md5(pwd); (pwdD.equals(md5Pwd))
|
代码生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.4.3.4</version> </dependency>
|
工具类,修改配置即可使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.nwa.until;
import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/weeklydb?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC", "root", "123456") .globalConfig(builder -> { builder.author("luckyNwa") .enableSwagger() .fileOverride() .outputDir(System.getProperty("user.dir") + "/src/main/java"); }) .packageConfig(builder -> { builder.parent("com.nwa") .moduleName("") .pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir") + "/src/main/resources/mapper")); }) .strategyConfig(builder -> { builder.addInclude("tb_weeks") .addTablePrefix("tb_", "sys_"); }) .templateEngine(new FreemarkerTemplateEngine()) .execute(); } }
|
Aop
springboot 中的自定义注解加上 aop
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
|
自定义注解类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.nwa.aop;
import java.lang.annotation.*;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysLog { String operationType() default "";
String operationName() default "";
String value() default ""; }
|
切面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| package com.nwa.aop;
import com.nwa.bean.Client; import com.nwa.service.impl.SysLogService; import com.nwa.until.ClientInfoUtil; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Date;
@Slf4j @Aspect @Component public class SysLogA { @Resource private SysLogService sysLogService;
@Pointcut("execution(* com.nwa.controller.IndexController.*(..))") public void logPointCut() { }
@After("logPointCut()") public void logTest(JoinPoint joinPoint) throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String browser = ClientInfoUtil.getBrowserInfo(request); String os = ClientInfoUtil.getOperatingSystem(request);
String ip = ClientInfoUtil.getIP(request);
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SysLog sysLog = method.getAnnotation(SysLog.class); String operationType = sysLog.operationType(); String operationName = sysLog.operationName();
Client client1 = new Client(); client1.setCreateBy(operationType);
client1.setRequestIp(ip);
client1.setLogTip(operationName); client1.setLogSys(os); client1.setLogBrowser(browser); client1.setCreateDate(new Date()); sysLogService.save(client1);
} catch (Exception e) { log.error("==后置通知异常=="); log.error("异常信息:{}------" + e.getMessage()); throw e; }
} }
接着只需要去Con那边对应的方法上面加入即可 @SysLog(operationType = "admin", operationName = "登录成功", value = "200")
|
Swagger
接着这个下面 2 个主题也同样是 api 生成
正常 1-2 依赖就能用了,为了好看加了 3 的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency>
|
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| package com.nwa.until;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ApiKey; import springfox.documentation.service.Contact; 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.List;
@Configuration @EnableSwagger2
public class SwaggerConfig { @Bean public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.nwa.controller"))
.build() .securitySchemes(security());
}
private ApiInfo apiInfo() { Contact luckyNWA = new Contact("luckyNWA", "www.nwa.com", "1656213092@qq.com"); return new ApiInfo( "小维的API接口说明", "此文档详细说明了小维前后端项目端口规范", "v1.0", "www.nwa.com", luckyNWA, "apache 2.0", "www.nwa.com", new ArrayList() ); }
private List<ApiKey> security() { ArrayList<ApiKey> list = new ArrayList<>(); list.add(new ApiKey("token", "token", "header")); return list;
}
}
|
other
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 控制层中 可以在类上面加 @Api(value = "提供了后台主页面的各种接口", tags = "后台主页面接口") 可以在方法上面加 @ApiOperation("修改密码") @ApiImplicitParams({ @ApiImplicitParam(dataType = "string", name = "userAcc", value = "账号", required = true), @ApiImplicitParam(dataType = "string", name = "newPassword", value = "新密码", required = true), @ApiImplicitParam(dataType = "string", name = "newPassword", value = "老密码", required = true), }) @PutMapping("/modifyPwd")
实体类中 可以在类上面加 @ApiModel(value = "周报里面的实体", description = "封装的周报对象") 可以在属性上面加 @ApiModelProperty("用户id")
|
knife4j
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.5</version> </parent>
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>2.0.9</version> </dependency>
|
YML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| knife4j: enable: true production: false basic: enable: false username: test password: 123 setting: enableSearch: false enableHomeCustom: true homeCustomLocation: classpath:markdown/home.md
|
去 resources–>新建 markdown—>home.md
1 2 3 4 5 6 7
| 昵称:Lucky 小 a
职业:全栈开发工程师
博客地址:http://luckynwa.top
联系邮箱:1656213092@qq.com
|
Knife4jConfiguration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| package com.nwa.config;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver; import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; 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.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
import java.util.List;
import static com.github.xiaoymin.knife4j.core.util.CollectionUtils.newArrayList;
@Configuration @AllArgsConstructor @EnableSwagger2WebMvc public class Knife4jConfiguration { private final OpenApiExtensionResolver openApiExtensionResolver;
@Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build() .extensions(openApiExtensionResolver.buildSettingExtensions()) .securitySchemes(security()); }
private List<ApiKey> security() { return newArrayList(new ApiKey("Token", "token", "header")); }
private ApiInfo apiInfo() { Contact luckyNWA = new Contact("luckyNWA", "http://luckynwa.top", "1656213092@qq.com"); return new ApiInfoBuilder() .title("小维的API接口说明") .description("多个应用系统之间的身份验证和访问控制的接口") .termsOfServiceUrl("http://luckynwa.top") .version("1.0").contact(luckyNWA).license("apache 2.0") .build(); }
}
|