You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

JIRA插件用户专属数据存储与REST API访问最优方案咨询

我之前做过类似的Jira插件开发需求,给你梳理下完全符合要求的最优实现方案——不用标签包裹输入字段,纯靠AJS调用REST API来实现用户专属数据的存储和展示:

第一步:后端REST API与数据持久化实现

首先得搭建后端的存储和查询接口,这里推荐用Jira官方支持的Active Objects做数据持久化,不用自己写SQL,还能和Jira的插件生命周期完美兼容。

1. 定义数据实体类

创建一个AO实体类,用来映射存储用户-工单的专属数据:

import net.java.ao.Entity;
import net.java.ao.Preload;
import net.java.ao.schema.StringLength;
import net.java.ao.schema.Unique;

@Preload
public interface UserIssueData extends Entity {
    // 当前用户的key
    @Unique(on = {"USER_KEY", "ISSUE_KEY"})
    String getUserKey();
    void setUserKey(String userKey);

    // 对应的工单key
    String getIssueKey();
    void setIssueKey(String issueKey);

    // 要存储的用户信息(示例字段,可按需扩展)
    @StringLength(StringLength.UNLIMITED)
    String getUserInfo1();
    void setUserInfo1(String userInfo1);

    String getUserInfo2();
    void setUserInfo2(String userInfo2);
}

2. 开发REST端点

创建REST资源类,提供保存和查询数据的接口,同时做权限校验,确保只有当前用户能访问自己的数据:

import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.user.ApplicationUser;
import net.java.ao.DBParam;
import net.java.ao.Query;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.Map;

@Path("/user-issue-data")
public class UserIssueDataResource {

    private final UserIssueDataDao userIssueDataDao;

    // 构造函数注入AO的DAO
    public UserIssueDataResource(UserIssueDataDao userIssueDataDao) {
        this.userIssueDataDao = userIssueDataDao;
    }

    // 保存用户数据
    @POST
    @Path("/save")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response saveUserData(Map<String, String> payload) {
        ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();
        if (currentUser == null) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        String issueKey = payload.get("issueKey");
        String userInfo1 = payload.get("userInfo1");
        String userInfo2 = payload.get("userInfo2");

        // 先查询是否已有该用户-工单的数据,有则更新,无则创建
        UserIssueData existingData = userIssueDataDao.findUnique(
                Query.select()
                        .where("USER_KEY = ? AND ISSUE_KEY = ?", currentUser.getKey(), issueKey)
        );

        if (existingData != null) {
            existingData.setUserInfo1(userInfo1);
            existingData.setUserInfo2(userInfo2);
            existingData.save();
        } else {
            userIssueDataDao.create(
                    UserIssueData.class,
                    new DBParam("USER_KEY", currentUser.getKey()),
                    new DBParam("ISSUE_KEY", issueKey),
                    new DBParam("USER_INFO1", userInfo1),
                    new DBParam("USER_INFO2", userInfo2)
            );
        }

        return Response.ok().build();
    }

    // 查询当前用户对应工单的数据
    @GET
    @Path("/get/{issueKey}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getUserData(@PathParam("issueKey") String issueKey) {
        ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();
        if (currentUser == null) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        UserIssueData data = userIssueDataDao.findUnique(
                Query.select()
                        .where("USER_KEY = ? AND ISSUE_KEY = ?", currentUser.getKey(), issueKey)
        );

        Map<String, String> result = new HashMap<>();
        if (data != null) {
            result.put("userInfo1", data.getUserInfo1());
            result.put("userInfo2", data.getUserInfo2());
        }

        return Response.ok(result).build();
    }
}

3. 在插件配置中注册REST和AO

atlassian-plugin.xml里添加以下配置:

<!-- 注册AO实体 -->
<ao key="user-issue-data-ao" class="com.your.plugin.package.UserIssueData"/>

<!-- 注册REST资源 -->
<rest-resource key="user-issue-data-rest" path="/user-issue-data" class="com.your.plugin.package.UserIssueDataResource">
    <description>REST endpoints for user-specific issue data</description>
</rest-resource>
第二步:前端右侧面板与AJS交互实现

接下来要在工单查看页面右侧添加自定义面板,用AJS动态渲染输入字段并调用REST API。

1. 注册Web Panel

atlassian-plugin.xml里定义右侧面板的位置:

<web-panel key="user-issue-data-panel" location="viewissue.right.context" weight="100">
    <description>User-specific data panel on issue view page</description>
    <label>我的专属数据</label>
    <resource name="view" type="download" location="/js/user-issue-panel.js"/>
</web-panel>

2. 编写AJS前端代码

创建/js/user-issue-panel.js,用AJS动态创建输入字段、绑定保存事件,并在页面加载时查询已有数据:

AJS.toInit(function() {
    // 获取当前工单的key(从页面DOM中提取)
    const issueKey = AJS.$('#key-val').text().trim();
    const panelContainer = AJS.$('<div id="user-issue-data-container" class="aui-panel-content"></div>');

    // 动态创建输入字段和按钮
    const input1 = AJS.$('<input type="text" id="user-info-1" placeholder="输入信息1" class="aui-field-input">');
    const input2 = AJS.$('<input type="text" id="user-info-2" placeholder="输入信息2" class="aui-field-input">');
    const saveBtn = AJS.$('<button id="save-user-data" class="aui-button aui-button-primary">保存</button>');
    const displayArea = AJS.$('<div id="saved-data-display" class="aui-message aui-message-success" style="display:none;margin-top:10px;"></div>');

    // 组装面板内容
    panelContainer.append(
        AJS.$('<div class="aui-field"><label>我的专属信息</label></div>'),
        input1,
        AJS.$('<br><br>'),
        input2,
        AJS.$('<br><br>'),
        saveBtn,
        displayArea
    );

    // 替换默认面板内容
    AJS.$('#user-issue-data-panel .aui-panel-content').replaceWith(panelContainer);

    // 加载已保存的数据
    function loadSavedData() {
        AJS.request('/rest/your-plugin-key/1.0/user-issue-data/get/' + issueKey, {
            method: 'GET',
            contentType: 'application/json',
            success: function(response) {
                const data = JSON.parse(response.responseText);
                if (data.userInfo1) input1.val(data.userInfo1);
                if (data.userInfo2) input2.val(data.userInfo2);
                updateDisplayArea(data);
            },
            error: function() {
                AJS.messages.error('#user-issue-data-container', {body: '加载数据失败,请稍后重试'});
            }
        });
    }

    // 更新展示区域
    function updateDisplayArea(data) {
        if (data.userInfo1 || data.userInfo2) {
            displayArea.html(`已保存信息:<br>信息1:${data.userInfo1 || '未填写'}<br>信息2:${data.userInfo2 || '未填写'}`).show();
        } else {
            displayArea.hide();
        }
    }

    // 绑定保存按钮事件
    saveBtn.click(function() {
        const payload = {
            issueKey: issueKey,
            userInfo1: input1.val().trim(),
            userInfo2: input2.val().trim()
        };

        AJS.request('/rest/your-plugin-key/1.0/user-issue-data/save', {
            method: 'POST',
            contentType: 'application/json',
            data: JSON.stringify(payload),
            success: function() {
                AJS.messages.success('#user-issue-data-container', {body: '数据保存成功'});
                updateDisplayArea(payload);
            },
            error: function() {
                AJS.messages.error('#user-issue-data-container', {body: '保存失败,请稍后重试'});
            }
        });
    });

    // 页面初始化时加载数据
    loadSavedData();
});
关键注意事项
  • 替换代码中的com.your.plugin.packageyour-plugin-key为你自己的插件包路径和插件key;
  • 权限控制:后端已经校验了当前登录用户,确保用户只能访问自己的数据;
  • 错误处理:前端添加了AJS的消息提示,提升用户体验;
  • 数据唯一性:AO实体里用@Unique注解确保每个用户-工单组合只有一条数据,避免重复存储。

按照这个流程实现,完全不需要用标签包裹输入字段,全程通过AJS动态渲染和调用REST API完成,是Jira插件开发中这类需求的标准最优方案。

内容的提问来源于stack exchange,提问作者Imamudin Naseem

火山引擎 最新活动