前言 公司的管理后台系统的前端部分都是部署到阿里云的 OSS(对象存储服务)里。最开始,团队成员都是手动上传 部署,但这样存在以下问题:
权限管理不当:需要给多人分配 OSS 访问权限,增加安全风险。
操作繁琐:每次发布都要手动执行上传操作,容易出错。
缺乏版本管理:如果出现问题,难以快速回滚到之前的版本。
为了解决这些问题,我决定开发一个 Jenkins 插件 ,让前端部署更自动化、可控化,同时减少不必要的 OSS 访问权限。
插件功能概述 这个 Jenkins 插件的主要功能包括:
全局配置 :管理员可以在 Jenkins 后台配置多个 OSS 服务器和授权。
任务配置 :在具体的 Jenkins 任务中,选择 OSS 服务器和上传目录。
自动备份 :每次部署前,会自动对原来的版本进行备份,以便回滚。
全局配置(管理员设置) 为了确保安全性,OSS 相关配置只允许 Jenkins 管理员 进行设置。
界面如下:
可以配置多个 OSS 服务器和授权。
AccessKeySecret 使用了 Jenkins 的密码凭证 进行存储,避免明文暴露。
配置完成后,可以点击 测试连接 按钮,确保 OSS 可用。
每次部署时,插件会自动备份 原有版本,以便紧急情况下回滚。
界面代码示例:
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 <?jelly escape-by-default='true'?> <j:jelly xmlns:j ="jelly:core" xmlns:st ="jelly:stapler" xmlns:d ="jelly:define" xmlns:l ="/lib/layout" xmlns:t ="/lib/hudson" xmlns:f ="/lib/form" xmlns:c ="/lib/credentials" > <f:section title ="阿里云OSS配置" > <f:entry title ="配置列表" > <f:repeatable var ="server" name ="servers" items ="${descriptor.servers}" > <table width ="100%" > <f:entry title ="唯一名称" description ="如:中台测试、订单中心测试、商品中心正式" > <f:textbox name ="name" value ="${server.name}" /> </f:entry > <f:entry title ="Endpoint" description ="如:oss-cn-beijing" > <f:textbox name ="endpoint" value ="${server.endpoint}" /> </f:entry > <f:entry title ="内网" description ="是否为阿里云内网" > <f:checkbox name ="internal" checked ="${server.internal}" /> </f:entry > <f:entry title ="BucketName" > <f:textbox name ="bucketName" value ="${server.bucketName}" /> </f:entry > <f:entry title ="AccessKeyId" > <f:textbox name ="accessKeyId" value ="${server.accessKeyId}" /> </f:entry > <f:entry title ="AccessKeySecret" > <f:password name ="accessKeySecret" value ="${server.accessKeySecret}" /> </f:entry > <f:entry title ="备份存储目录" description ="远程原文件备份存储目录,通常保持默认即可。" > <f:textbox name ="backupFolder" value ="${server.backupFolder}" default ="__backup__/" /> </f:entry > <f:entry title ="上传临时目录" description ="上传时的远程临时目录,通常保持默认即可。" > <f:textbox name ="temporaryFolder" value ="${server.temporaryFolder}" default ="__temporary__/" /> </f:entry > <f:entry title ="最大备份数" > <f:textbox name ="backupMaxNumber" value ="${server.backupMaxNumber}" default ="3" /> </f:entry > <f:validateButton title ="测试连接" progress ="连接中..." method ="testConnection" with ="endpoint,internal,bucketName,accessKeyId,accessKeySecret" /> <f:entry title ="" > <div align ="right" > <f:repeatableDeleteButton /> </div > </f:entry > </table > </f:repeatable > </f:entry > </f:section > </j:jelly >
任务配置 任务的配置非常简单,用户只需要:
选择已配置的 OSS 服务器 。
待上传的内容 。
指定要上传的目录 。
界面如下:
界面代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 <?jelly escape-by-default='true'?> <j:jelly xmlns:j ="jelly:core" xmlns:st ="jelly:stapler" xmlns:d ="jelly:define" xmlns:l ="/lib/layout" xmlns:t ="/lib/hudson" xmlns:f ="/lib/form" > <f:entry title ="OSS服务器" field ="serverName" > <f:select /> </f:entry > <f:entry title ="本地目录" field ="localFolder" description ="待上传的目录,可使用jenkins变量。" > <f:textbox default ="${descriptor.getLocalFolderDefaultValue()}" /> </f:entry > <f:entry title ="远程目录" field ="remoteFolder" description ="远程目录,用 / 结尾,如:user-center/ 。为空时则表示根目录。" > <f:textbox /> </f:entry > </j:jelly >
任务构建(文件上传) 上传文件的部分逻辑较为常规,主要使用 aliyun-sdk-oss 提供的 API 进行操作。
建议参考官方文档:阿里云 OSS 官方 SDK 文档
部分 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 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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 public class AliyunOssPublisher extends Builder { @Override public boolean perform (AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { logger = listener.getLogger(); AliyunOssServer server = findServerByName(serverName); if (server == null ) { logger.println("未能找到名为 " + serverName + " 的服务配置" ); return false ; } if (publish()) { logger.println("发布成功" ); logger.println("准备收尾工作" ); try { cleanBackup(); shutdown(); } catch (Exception e) { logger.println(desensitization(e.getMessage())); } logger.println("收尾完成" ); return true ; } return false ; } private boolean publish () { try { return true ; } catch (Exception e) { return false ; } } private void cleanBackup () { logger.println("开始检查并清理多余备份内容" ); } public void shutdown () { if (client != null && client.sdk != null ) { client.sdk.shutdown(); } } @Extension public static final class AliyunOssPublisherDescriptor extends BuildStepDescriptor <Builder> { private List<AliyunOssServer> servers = new ArrayList <>(); public AliyunOssPublisherDescriptor () { super (AliyunOssPublisher.class); load(); } public AliyunOssServer[] getServers() { return servers.toArray(new AliyunOssServer [0 ]); } @Override public boolean isApplicable (Class<? extends AbstractProject> aClass) { return true ; } @Override public String getDisplayName () { return "发布到阿里云OSS" ; } @Override public boolean configure (StaplerRequest req, JSONObject formData) throws FormException { } public String getLocalFolderDefaultValue () { return "${WORKSPACE}/build" ; } public ListBoxModel doFillServerNameItems () { ListBoxModel m = new ListBoxModel (); AliyunOssServer[] servers = getServers(); for (AliyunOssServer server : servers) { m.add(server.getName()); } return m; } public FormValidation doTestConnection (@QueryParameter("endpoint") String endpoint, @QueryParameter("internal") Boolean internal, @QueryParameter("bucketName") String bucketName, @QueryParameter("accessKeyId") String accessKeyId, @QueryParameter("accessKeySecret") String accessKeySecret) throws Exception { AliyunOssClient client = new AliyunOssClient (endpoint, internal, bucketName, accessKeyId, SecretHelper.decrypt(accessKeySecret)); try { client.test(); return FormValidation.ok("链接成功" ); } catch (Exception e) { return FormValidation.error("链接失败:" + e.getMessage()); } } } }
入门教程 如果你是第一次开发 Jenkins 插件,推荐先阅读官方的入门教程:Jenkins Plugin 开发入门 。
这个教程简单直接,可以帮助你快速了解 Jenkins 插件的开发流程,避免走弯路。
如何进阶 Jenkins 官方的入门教程比较基础,要真正开发一个插件,你可能会遇到一些文档里没有覆盖的问题。我的建议是:
找类似插件的源码 :比如一些已有的 Jenkins OSS 上传插件,看看它们是如何实现的。
如何调试 参考:https://blog.csdn.net/xueerfei008/article/details/81871241
文章中是以 MacOS 为例子。
如何运行 在命令行中执行:mvn hpi:run
如何打包 在命令行中执行:mvn clean install
如何发版 修改 pom.xml 中的版本号,再执行 打包命令,打包得到 hpi 文件。
结语 这次是我第一次正式用 Java 开发,过程充满挑战,但也收获颇丰。希望这篇文章能帮助到想开发 Jenkins 插件的同学。🎉
如果你有更好的建议或者遇到问题,欢迎交流!