首页 文章 精选 留言 我的

精选列表

搜索[客户分析],共10000篇文章
优秀的个人博客,低调大师

开源中国客户端 Android 10 经验适配指南,含代码

我们App的适配从 targetSdkVersion = 26跨版本升级到29,因此会遇到大量的坑,最终的版本配置如下: 现在进入填坑适配指南,包含实际经验代码,绝不照搬翻译文档 1.Region.Op相关异常:java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed 当targetSdkVersion >=Build.VERSION_CODES.P 时调用 canvas.clipPath(path, Region.Op.XXX); 引起的异常,参考源码如下: @Deprecated public boolean clipPath(@NonNull Path path, @NonNull Region.Op op) { checkValidClipOp(op); return nClipPath(mNativeCanvasWrapper, path.readOnlyNI(), op.nativeInt); } private static void checkValidClipOp(@NonNull Region.Op op) { if (sCompatiblityVersion >= Build.VERSION_CODES.P && op != Region.Op.INTERSECT && op != Region.Op.DIFFERENCE) { throw new IllegalArgumentException( "Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed"); } } 我们可以看到当目标版本从Android P开始,Canvas.clipPath(@NonNull Path path, @NonNull Region.Op op) ; 已经被废弃,而且是包含异常风险的废弃API,只有Region.Op.INTERSECT 和 Region.Op.DIFFERENCE 得到兼容,目前不清楚google此举目的如何,仅仅如此简单就抛出异常提示开发者适配,几乎所有的博客解决方案都是如下简单粗暴: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { canvas.clipPath(path); } else { canvas.clipPath(path, Region.Op.XOR);// REPLACE、UNION 等 } 但我们一定需要一些高级逻辑运算效果怎么办?如小说的仿真翻页阅读效果,解决方案如下,用Path.op代替,先运算Path,再给canvas.clipPath: if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){ Path mPathXOR = new Path(); mPathXOR.moveTo(0,0); mPathXOR.lineTo(getWidth(),0); mPathXOR.lineTo(getWidth(),getHeight()); mPathXOR.lineTo(0,getHeight()); mPathXOR.close(); //以上根据实际的Canvas或View的大小,画出相同大小的Path即可 mPathXOR.op(mPath0, Path.Op.XOR); canvas.clipPath(mPathXOR); }else { canvas.clipPath(mPath0, Region.Op.XOR); } 2.明文HTTP限制 当targetSdkVersion >=Build.VERSION_CODES.P 时,默认限制了HTTP请求,并出现相关日志: java.net.UnknownServiceException: CLEARTEXT communication to xxx not permitted by network security policy 第一种解决方案:在AndroidManifest.xml中Application添加如下节点代码 <application android:usesCleartextTraffic="true"> 第二种解决方案:在res目录新建xml目录,已建的跳过 在xml目录新建一个xml文件network_security_config.xml,然后在AndroidManifest.xml中Application添加如下节点代码 android:networkSecurityConfig="@xml/network_config" 名字随机,内容如下: <?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config> 3.Android Q(10)中的媒体资源读写 相关的Android Q 行为变更不做细说,网上大部分博客关于Android Q 适配都在说行为变更,我们将根据实际遇到的问题,实际解决 1、扫描系统相册、视频等,图片、视频选择器都是通过ContentResolver来提供,主要代码如下: private static final String[] IMAGE_PROJECTION = { MediaStore.Images.Media.DATA, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media._ID, MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; Cursor imageCursor = mContext.getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION, null, null, IMAGE_PROJECTION[4] + " DESC"); String path = imageCursor.getString(imageCursor.getColumnIndexOrThrow(IMAGE_PROJECTION[0])); String name = imageCursor.getString(imageCursor.getColumnIndexOrThrow(IMAGE_PROJECTION[1])); int id = imageCursor.getInt(imageCursor.getColumnIndexOrThrow(IMAGE_PROJECTION[2])); String folderPath = imageCursor.getString(imageCursor.getColumnIndexOrThrow(IMAGE_PROJECTION[3])); String folderName = imageCursor.getString(imageCursor.getColumnIndexOrThrow(IMAGE_PROJECTION[4])); //Android Q 公有目录只能通过Content Uri + id的方式访问,以前的File路径全部无效,如果是Video,记得换成MediaStore.Videos if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ path = MediaStore.Images.Media .EXTERNAL_CONTENT_URI .buildUpon() .appendPath(String.valueOf(id)).build().toString(); } 2、判断公有目录文件是否存在,自Android Q开始,公有目录File API都失效,不能直接通过new File(path).exists();判断公有目录文件是否存在,正确方式如下: public static boolean isAndroidQFileExists(Context context, String path){ if (context == null) { return false; } AssetFileDescriptor afd = null; ContentResolver cr = context.getContentResolver(); try { Uri uri = Uri.parse(path); afd = cr.openAssetFileDescriptor(Uri.parse(path), "r"); if (afd == null) { return false; } else { close(afd); } } catch (FileNotFoundException e) { return false; }finally { close(afd); } return true; } 3、保存或者下载文件到公有目录,保存Bitmap同理,如Download,MIME_TYPE类型可以自行参考对应的文件类型,这里只对APK作出说明 public static void copyToDownloadAndroidQ(Context context, String sourcePath, String fileName, String saveDirName){ ContentValues values = new ContentValues(); values.put(MediaStore.Downloads.DISPLAY_NAME, fileName); values.put(MediaStore.Downloads.MIME_TYPE, "application/vnd.android.package-archive"); values.put(MediaStore.Downloads.RELATIVE_PATH, "Download/" + saveDirName.replaceAll("/","") + "/"); Uri external = MediaStore.Downloads.EXTERNAL_CONTENT_URI; ContentResolver resolver = context.getContentResolver(); Uri insertUri = resolver.insert(external, values); if(insertUri == null) { return; } String mFilePath = insertUri.toString(); InputStream is = null; OutputStream os = null; try { os = resolver.openOutputStream(insertUri); if(os == null){ return; } int read; File sourceFile = new File(sourcePath); if (sourceFile.exists()) { // 文件存在时 is = new FileInputStream(sourceFile); // 读入原文件 byte[] buffer = new byte[1444]; while ((read = is.read(buffer)) != -1) { os.write(buffer, 0, read); } is.close(); os.close(); } } catch (Exception e) { e.printStackTrace(); } finally { close(is,os); } } 4、保存图片相关 /** * 通过MediaStore保存,兼容AndroidQ,保存成功自动添加到相册数据库,无需再发送广告告诉系统插入相册 * * @param context context * @param sourceFile 源文件 * @param saveFileName 保存的文件名 * @param saveDirName picture子目录 * @return 成功或者失败 */ public static boolean saveImageWithAndroidQ(Context context, File sourceFile, String saveFileName, String saveDirName) { String extension = BitmapUtil.getExtension(sourceFile.getAbsolutePath()); ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DESCRIPTION, "This is an image"); values.put(MediaStore.Images.Media.DISPLAY_NAME, saveFileName); values.put(MediaStore.Images.Media.MIME_TYPE, "image/png"); values.put(MediaStore.Images.Media.TITLE, "Image.png"); values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + saveDirName); Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; ContentResolver resolver = context.getContentResolver(); Uri insertUri = resolver.insert(external, values); BufferedInputStream inputStream = null; OutputStream os = null; boolean result = false; try { inputStream = new BufferedInputStream(new FileInputStream(sourceFile)); if (insertUri != null) { os = resolver.openOutputStream(insertUri); } if (os != null) { byte[] buffer = new byte[1024 * 4]; int len; while ((len = inputStream.read(buffer)) != -1) { os.write(buffer, 0, len); } os.flush(); } result = true; } catch (IOException e) { result = false; } finally { Util.close(os, inputStream); } return result; } 4.EditText默认不获取焦点,不自动弹出键盘 该问题出现在targetSdkVersion >=Build.VERSION_CODES.P 情况下,且设备版本为Android P以上版本,目前我们没有从源码中查到相关判断改动,解决方法在onCreate中加入如下代码: mEditText.post(() -> { mEditText.requestFocus(); mEditText.setFocusable(true); mEditText.setFocusableInTouchMode(true); }); 5.Only fullscreen activities can request orientation 异常 该问题出现在targetSdkVersion >=Build.VERSION_CODES.O_MR1 ,也就是 API 27,当设备为Android 26时(27以上已经修复,也许google觉得不妥当,又改回来了),如果非全面屏透明activity固定了方向,则出现该异常,但是当我们在小米、魅族等Android 26机型测试的时候,并没有该异常,华为机型则报该异常,这是何等的卧槽。。。没办法,去掉透明style或者去掉固定方向代码即可,其它无解 6.安装APK Intent及其它文件相关Intent /* * 自Android N开始,是通过FileProvider共享相关文件,但是Android Q对公有目录 File API进行了限制 * 从代码上看,又变得和以前低版本一样了,只是必须加上权限代码Intent.FLAG_GRANT_READ_URI_PERMISSION */ private void installApk() { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ //适配Android Q,注意mFilePath是通过ContentResolver得到的,上述有相关代码 Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(mFilePath) ,"application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(intent); return ; } File file = new File(saveFileName + "osc.apk"); if (!file.exists()) return; Intent intent = new Intent(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(getApplicationContext(), "net.oschina.app.provider", file); intent.setDataAndType(contentUri, "application/vnd.android.package-archive"); } else { intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } startActivity(intent); } 我们APK开发实践中暂时遇到的坑就这些,当然Android Q的改动是相当大的,例如还有App私有沙箱文件、定位权限和后台弹出Activity限制,这些都必须根据自身实践去踩坑适配,有条件的尽可能去阅读官方文档,参考改进。

优秀的个人博客,低调大师

[雪峰磁针石博客]python库介绍-telnetlib:Telnet客户端

python测试开发项目实战-目录 python工具书籍下载-持续更新 python 3.7极速入门教程 - 目录 简介 telnetlib模块提供的Telnet类实现了Telnet协议(参见rfc854)。它为协议字符和telnet选项提供符号常量,符号常量来源于arpa/telnet.h,去掉了前缀TELOPT_。 telnet命令有: IAC, DONT, DO, WONT, WILL, SE (Subnegotiation End), NOP (No Operation), DM (Data Mark), BRK (Break), IP (Interrupt process), AO (Abort output), AYT (Are You There), EC (Erase Character), EL (Erase Line), GA (Go Ahead), SB (Subnegotiation Begin)。 class telnetlib.Telnet(host=None, port=0[, timeout]) 快速入门 #!/usr/bin/env python # -*- coding: utf-8 -*- # Author Rongzhong Xu 2016-08-25 wechat: pythontesting """ Name: telnet_demo.py Tesed in python3.5 """ import telnetlib HOST = "172.17.100.18" user = "test" password = "123456" def command(con, flag, str_=""): data = con.read_until(flag.encode()) print(data.decode(errors='ignore')) con.write(str_.encode() + b"\n") return data tn = telnetlib.Telnet(HOST) command(tn, "login: ", user) if password: command(tn, "Password: ", password) command(tn, "$", "ls") command(tn, "$", " exit") command(tn, "$", "") tn.close() 执行结果: $ python3 telnet_demo.py CentOS release 6.7 (Final) Kernel 2.6.32-573.18.1.el6.x86_64 on an x86_64 login: Password: Last login: Thu Aug 25 17:20:18 from 172.17.153.6 [test@public01 ~]$ ls [test@public01 ~]$ exit logout 参考资料 python测试等IT技术支持qq群: 144081101(后期会录制视频存在该群群文件) 591302926 567351477 本文最新版本地址 本文涉及的python测试开发库 谢谢点赞! 本文相关海量书籍下载 python官方文档:https://docs.python.org/3/library/telnetlib.html effbot: http://effbot.org/librarybook/telnetlib.htm rfc854: https://tools.ietf.org/html/rfc854.html 方法 Telnet.read_until(expected[, timeout]) 读取直到遇到了给定的字符串expected或超时秒数。 当找不到匹配时,返回读取内容。连接关闭或者没有数据时触发EOFError异常。 Telnet.read_all() 读取所有数据直到EOF,阻塞直到连接关闭。 Telnet.read_some() 读取至少一个字节的数据,除非EOF。 如果没有数据则阻塞。 Telnet.read_very_eager() 非阻塞地读取。连接关闭或者没有数据时触发EOFError异常。返回b''如果没有数据, Telnet.read_eager() 读取的已有数据。(快) 连接关闭或者没有数据时触发EOFError异常。返回b''如果没有数据, Telnet.read_lazy() 读取的已有数据(懒) 连接关闭或者没有数据时触发EOFError异常。返回b''如果没有数据, Telnet.read_very_lazy() 读取的已有数据(很懒) 连接关闭或者没有数据时触发EOFError异常。返回b''如果没有数据, Telnet.read_sb_data() 返回的SB/SE pair (suboption begin/end)之间的数据。此方法永远不会阻塞。 Telnet.open(host[, port[, timeout]]) 连接到主机。可选的第二个参数是默认为标准的 Telnet 端口 (23) 的端口号。可选的超时参数指定以秒计) 阻塞操作 (如连接尝试超时 (如果不指定,将使用全局默认超时设置)。 Telnet.msg(msg[, *args]) 当调试级别为> 0 打印调试消息。 Telnet.set_debuglevel(debuglevel) 设置调试级别。debuglevel越高信息越多。 Telnet.close() 关闭该连接。 Telnet.get_socket() 返回套接字供内部使用。 Telnet.fileno() 返回套接字对象内部使用的文件描述符。 Telnet.write(buffer) 写入字符串到套接字,加倍 IAC 的任何字符。连接关闭是可能触发OSError异常。 Telnet.interact() 交由用户控制。 Telnet.mt_interact() 多线程的版本的interact()。 Telnet.expect(list[, timeout]) 读取直到匹配正则表达式项列表中的一个。 list是一个正则表达式列表,包含编译(regex 对象)或未编译(字节字符串)。timeout以秒为单位, 默认值为无限期阻塞。 返回元组的三个项目: index为匹配正则表达式的位置;match 对象;此时读了的字节。 文件结尾或没有数据时触发EOFError异常,不匹配时返回(-1, None, data)。 如果正则表达式是贪婪模式或者如果多个表达式可以匹配相同的输入,结果是不确定的,可能取决于I/O定时。 Telnet.set_option_negotiation_callback(callback) 每次从输入流读取telnet选项时,调用callback(telnet socket, command (DO/DONT/WILL/WONT), option),后续步骤不会执行。

优秀的个人博客,低调大师

30家客户案例带你看蚂蚁金服全面科技开放

小蚂蚁说: “蚂蚁技术,成熟一个开放一个。”去年ATEC大会上,蚂蚁金服发布的开放原则还犹在耳边,如今新一年的ATEC又来了。 跟我们一起来看看今年蚂蚁金服的ATEC会给大家带来哪些惊喜吧! 蚂蚁一共开放了哪些技术? 蚂蚁开放的技术解决了什么实际问题? 这些日常生活中和老百姓息息相关的问题,是由蚂蚁的哪些技术解决的? 因为蚂蚁的技术开放,老百姓的日常生活发生了怎样的改变? 为了回答这些问题,特别在今年ATEC大会前,筹划了这组海报。通过30个场景的时代变迁,讲述了蚂蚁金融科技如何改变大家付款、做生意、购买保险、借贷以及投资的方式。 有网友为这个系列海报取了一个名字:《BEFORE & AFTER》;有网友唏嘘感叹,科技如何惊艳了时光;更有网友发现,不经意间,蚂蚁金服的技术已经渗透在智慧城市的方方面面。 事实上,这些反馈正是我们想要传递的信息

优秀的个人博客,低调大师

Spring 项目如何优雅的生成接口文档与客户端

背景 在开发 Restful 服务的过程中,大家或多或少都会碰到类似的问题,比如:接口如何文档化、怎样自动生成 Client。我们在开发 DMS 的过程,也碰到了类似的问题,并且积累了一些经验,借此跟大家分享,希望抛砖引玉。 概述 首先整体说下总的技术方案就是 Spring Boot + Springfox + Swagger Codegen ,其中 Spring Boot + Springfox 主要解决了接口如何文档化问题;Swagger Codegen 则主要解决了如何自动生成 Client 的问题。下面就详细介绍这套方案的实现细节。 具体方案 接口文档化 Spring Boot + Springfox Springfox 是为基于 Spring 构建的接口自动生成文档的工具,它的原理就是根据 Spring 接口层的注解生成符合 Swagger 规范的接口描述文件,然后通过内嵌的 Swagger UI 解析该描述文件并渲染出来。 对于这个这个方案,知道的人比较多,内网也有很多介绍该方案的文章,附录里我罗列了些,在此我就不具体阐述,下面我只介绍下我们使用的一些经验。 @Api 的 tags 属性请使用英文,该属性值在 Codegen 的时候会作为模块名。 创建多版本 Api 的方法 @Configuration @EnableSwagger2 @ComponentScan(basePackages = {"com.tmall.pegasus.dms.controller"}) public class SwaggerConfiguration { @Bean public Docket v20DocumentationPlugin() { return new VersionedDocket("2.0"); } @Bean public Docket v10DocumentationPlugin() { return new VersionedDocket("1.0"); } class VersionedDocket extends Docket { public VersionedDocket(String version) { super(DocumentationType.SWAGGER_2); super.groupName(version) .select() .apis(RequestHandlerSelectors.any()) .paths(regex(API_BASE_PATH + "/.*")) .build() .apiInfo(getApiInfo(version)) .pathProvider(new BasePathAwareRelativePathProvider(API_BASE_PATH)) // 这里记得设置 protocols,不然 Codegen 默认生成的 basePath 是 https 协议 .protocols(Sets.newHashSet("http")) .directModelSubstitute(LocalDate.class, String.class) .genericModelSubstitutes(ResponseEntity.class); } private ApiInfo getApiInfo(String version) { return new ApiInfo("Test Api", "Test Api Documentation", "1.0", "urn:tos", new Contact("xiaoming", "", "xiaoming@qq.com"), "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList()); } } @Bean UiConfiguration uiConfig() { return UiConfigurationBuilder.builder() .deepLinking(true) .displayOperationId(false) .defaultModelsExpandDepth(1) .defaultModelExpandDepth(1) .defaultModelRendering(ModelRendering.EXAMPLE) .displayRequestDuration(false) .docExpansion(DocExpansion.NONE) .filter(false) .maxDisplayedTags(null) .operationsSorter(OperationsSorter.ALPHA) .showExtensions(false) .tagsSorter(TagsSorter.ALPHA) .supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS) .validatorUrl(null) .build(); } class BasePathAwareRelativePathProvider extends AbstractPathProvider { private String basePath; public BasePathAwareRelativePathProvider(String basePath) { this.basePath = basePath; } @Override protected String applicationPath() { return basePath; } @Override protected String getDocumentationPath() { return "/"; } @Override public String getOperationPath(String operationPath) { UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromPath("/"); return Paths.removeAdjacentForwardSlashes( uriComponentsBuilder.path(operationPath.replaceFirst(basePath, "")).build().toString()); } } } 在 Controller 上统一加上接口的前缀,尤其接口有多个版本的时候,该方式会非常方便。 @RequestMapping(path = "/api/v1") public class DataController { } @RequestMapping(path = "/api/v2") public class DataControllerV2 { } 方法注解记得设置 nickName,设置该属性的好处是在 Codegen 的时候会用该属性值作为对应的接口方法名。 @ApiOperation(value = "新建数据", nickname = "createContent") 自动生成 Client Swagger Codegen, 官方 Usage Doc 在让业务方使用我们接口的时候,自然而然的我们需要提供对应的 Client,如果一个个手动封装接口,会带来很多的重复工作,维护起来也很麻烦,于是我们想寻求一些自动生成的方案。后来发现 Swagger 官方已经帮我们想好了,对应的就是 Swagger Codegen,它的原理也不复杂,就是基于 Swagger 的接口描述文件生成 Client 代码,当然它也支持生成 Server 端的项目骨架。 下面,我就详细说下我们的使用经验。 多模块项目生成 Client 针对多模块项目,我们会期望 Client 的 pom 可以自定义,因为 client 会依赖 common 的一些 Model 类,另外会依赖 Parent Pom,所以不能通过 codegen 生成。这个可以通过 .swagger-codegen-ignore 实现,该文件你可以理解为类似 .gitignore 的文件,在 codegen 的时候会忽略生成该文件下的文件列表。 .swagger-codegen-ignore 要放到 client 目录下,配置的路径都是相对 Client 目录而言,你可以把所有不想生成的文件都列在里面,下面就是一个具体示例: build.sh build.sbt build.gradle gradle.properties gradlew gradlew.bat pom.xml README.md settings.gradle .gitignore gradle .swagger-codegen/VERSION docs/** git_push.sh .travis.yml src/main/AndroidManifest.xml 推荐通过 maven 插件配置生成 Client。 对应的插件配置示例如下: <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>clean-additional-generated-files</id> <phase>generate-sources</phase> <goals> <goal>clean</goal> </goals> <configuration> <excludeDefaultDirectories>true</excludeDefaultDirectories> <filesets> <fileset> <directory>${project.basedir}/src/main/java</directory> <!--<directory>${project.basedir}/src/test</directory>--> </fileset> </filesets> </configuration> </execution> </executions> </plugin> <plugin> <groupId>io.swagger</groupId> <artifactId>swagger-codegen-maven-plugin</artifactId> <version>2.3.1</version> <executions> <execution> <goals> <goal>generate</goal> </goals> <configuration> <templateDirectory>${project.basedir}/src/main/resources/template</templateDirectory> <inputSpec>${project.basedir}/src/main/resources/api.json</inputSpec> <!--<inputSpec>http://localhost:7001/v2/api-docs?group=1.0</inputSpec>--> <language>java</language> <output>${project.basedir}</output> <importMappings> <importMapping>ContentInfo=com.tmall.pegasus.dms.common.result.ContentInfo</importMapping> <importMapping>DataContent=com.tmall.pegasus.dms.common.params.DataContent</importMapping> <importMapping>DeliveryInfo=com.tmall.pegasus.dms.common.result.DeliveryInfo</importMapping> <importMapping>FieldInfo=com.tmall.pegasus.dms.common.result.FieldInfo</importMapping> <importMapping>ResourceInfo=com.tmall.pegasus.dms.common.result.ResourceInfo</importMapping> </importMappings> <invokerPackage>com.tmall.pegasus.dms.client</invokerPackage> <apiPackage>com.tmall.pegasus.dms.client.api</apiPackage> <modelPackage>com.tmall.pegasus.dms.common.result</modelPackage> <library>feign</library> <configOptions> <sourceFolder>src/main/java</sourceFolder> <ignoreFileOverride>${project.basedir}/.swagger-codegen-ignore</ignoreFileOverride> </configOptions> </configuration> </execution> </executions> </plugin> 第一个插件是 maven-clean-plugin,它帮助我们 clean 掉上次生成的代码,第二个就是 maven-swagger-codegen 插件,可以看出里面的配置非常丰富,各个配置的含义都可以在 官方文档 找到,有几个配置我觉得会常用到,下面简单介绍下。 importMappings 当你不想用 Swagger Codegen 自己生成的 Model 类,而是想用自己 Common 包里的 Model 时,你可以用该配置实现,配置项就是一个个的映射关系,在 Codegen 的时候会将 Model 引用替换成你设置的值。 apiPackage 就是你接口的包名 library Codegen 默认支持多种 http client ,你可以通过该参数指定。 ignoreFileOverride 就是 .swagger-codegen-ignore 对应的路径。 templateDirectory 你如果想自定义生成 Client 代码,可以通过自定义 Generator 实现,你如果只是想对系统生成的 Client 做微小调整,可以通过修改系统自带的的 Template 实现。方法就是把 Swaggen Codegen 的模板文件拷贝一份到你的 src/main/resources 里,直接改里面的文件,同时记得配置 templateDirectory 的路径,再重新生成即可。 将生成文件均放到 .gitignore 中。这样的好处是可以避免大量无意义的提交其他 ==== 本文更多介绍的是这套方案中我们关注的点,并没有一步步的介绍开发步骤,因为这方面的资料谷歌上有很多,大家可以自行查阅。如果阅读中有不理解或疑惑的点,欢迎钉钉 @澶渊 、@乱我。 另外说点自己开发的一些感想,在开发的过程中,应尽可能的让自己变的 “懒” 一些,要对 “重复” 保持足够的敏感,每当感觉有重复代码出现时,就要考虑这里是否可以复用,是否可以通过 AOP 实现,是否可以自动生成,总的来说就是用更少的代码做更多的事,最后谢谢阅读本文。

优秀的个人博客,低调大师

WEB API已成为构建客户端服务的利器?

一、软件开发的互联网思维 1.网络就是计算机 进入互联网时代,软件开发必然放眼全局,充分利用互联网的资源和特性。比如,为了提高响应性能,就选择使用CDN作为前端缓存。而且可能还需要采用CDN提供的API进行编程,以便更加优化地调度缓存资源。类似这样可利用的网络资源很多,开发者需要统一考虑到,也就是建立起软件开发的互联网思维。这样开发出来的软件,才具有互联网基因,能够适应互联网的时代。 2. 云计算助力软件开发 云计算通过虚拟化技术把很多资源整合起来,软件开发理念不断向更高维度抽象。从虚拟机到集群,再到容器,再抽象成函数计算,软件开发越来越聚焦在业务核心上。这样的演进,必然带来效率的提升。基于云计算环境的软件,不可避免地需要通过各种API来进行资源管理。 3. WEB API的作用 1)开放有价值的数据 一些数据具有较强的

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册