Java文档自动化生成

发表信息: by

Java文档自动化生成

0. 问题

我们组作为基础数据组,很多服务大都是对外提供基础数据的操作, 接口非常多, 接入方也非常多。

现在提供接口文档的方式是 在wiki中开一个页面,用来手写这个服务的接口文档, 比如像下面这样:


package xxxx.xxxx
  
 
public interface XxxxSoaService {
    /**
     * 根据XxxID查询
     *
     * @param Xxxx参数
     * @return Xxxx信息
     */
    XxxxDTO getXxxxInfo(long Xxxx);
}
  
public class XxxxDTO {
    /**
     * XxxxID
     */
    private long Xxxxx;
  
    /**
     * Xxxx名称
     */
    private String name;
    /**
     * Xxxx有效无效状态
     */
    private DataStatusDTO dataStatusDTO;
  
    /**
     * Xxxx类型
     */
    private ShopTypeDTO shopTypeDTO;
}
  
  
public enum  DataStatusDTO {
    VALID(1, "有效"),
    INVALID(0, "无效");
  
    private int code;
    private String description;
}
  
public enum ShopTypeDTO {
    NORMAL(0, "正式"),
    TEST(100, "测试");
  
    private int code;
    private String description;
}

每次给外部提供一个新接口,都要手写这些东西, 每次更新了接口信息,也要手动更新wiki里面的文档, 如果忘记更新了,那么就会坑别人。 着实费事!

1. 设想

如果每次编写完代码并提交代码后, 文档会自动生成到一个地方并且通过web的方式展现出来,那该多好, 以后的接口文档里面就可以写成这样子:

# 服务依赖说明
 
 
## ALPHA
## BETA
## PROD
 
 
# 服务集群说明
巴拉巴拉
 
 
# 包依赖
巴拉巴拉
 
 
# 接口定义
## XXXX功能接口
`xxxx.xxxx.XxxxSoaService.getXxxxInfo` 详情见下面JavaDoc
 
 
# JavaDoc
url: xxxxxxxx

只需要在文档里面给出服务依赖相关的内容, 和某个功能的接口是什么, 具体接口详情看javadoc就好了

期望具体生成文档的流程自动化成下面的样子

image

开发人员在个人PC上面push代码到gitlab, gitlab通知Server生成文档, server生成文档后部署页面,最终将web页面展现给个人PC

2. 研究

针对上面的问题,有几个点需要确认, 如果确认好下面这几个点, 整个流程就通了

2.1 push 代码到gitlab后,gitlab怎么将信号给到server?

这一点有两种办法:

一种是通过jenkins, 我之前也玩过,具体玩法可以参考以前写的一篇博客:http://int32.me/blog/2017/09/18/jenkins/, 这种玩法需要安装jenkins, 而且比较笨重

另一种是通过gitlab自带的CI/CD工具 runner, 具体什么是runner可以参考下面几篇文章:

  • https://www.cnblogs.com/cnundefined/p/7095368.html
  • https://blog.csdn.net/u011215669/article/details/80446624
  • https://www.jianshu.com/p/306cf4c6789a

2.2 server接受到信号后怎么根据信号生成文档?

当server接受到信号后,怎么生成文档呢? 我最终选择javadoc形式, 但是javadoc必须要有java代码才能生成文档, 所以就好从gitlab上把java代码clone下来, 不过不需要担心这个, 因为 gitlab的CI/CD 和 jenkins都有这样的功能, 把代码clone到一个临时空间, 当执行完所有动作后便将代码删除。

那么新的问题来了, 如何根据java代码生成java doc, 这个也好解决, 因为我们项目都是gradle的项目, 所以根据gradle集成的doc命令就可以将java代码生成javadoc

2.3 生成文档后怎么将文档部署成web页面?

这个最终我选择了nginx, 因为javadoc是现成的html文档, 所以怎么将html文档显示出来就是要考虑的了, 而nginx正好能做到这一点。

在考虑这一问题的时候, 如果要简单方便, 就将生成的javadoc文档直接放在nginx读取的目录下就行了, 但是这块可以做的更好, 比如有所有文档根据 项目 → 分支 → 文档 的顺序存放,然后提供一个索引页面

这样每次生成文档, 都是生成在项目/分支下, 每个人的文档都不会相互冲突, 方便管理查看。 这个也有办法接口, 使用nginx天生的文件服务的功能。

3. 开做

3.1 找个服务器安装gitlab runner

gitlab官方安装链接

这里只给出centos的安装方式


# 1 下载runner
` sudo wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64`

# 2 修改权限
`sudo chmod +x /usr/local/bin/gitlab-runner`

# 3 创建用户
`sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash`

# 4 安装server
`sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner`

# 5 起送runenr
`sudo gitlab-runner start`
# 6 注册runner
`sudo gitlab-runner register`

# 7 根据提示输入相关信息 
这里url 和 token 可以在gitlab->Project->Settings->CI/CD -> runner信息中找到

image

3.2 将项目注册到自己的runner上

上面runner注册成功后, 便能发现自己的项目上会出现刚刚注册的runner

image

3.3 修改gradle文件, 方便生成javadoc

configurations {
    doc {
        transitive false
    }
}
  
  
javadoc {
    options.charSet = 'UTF-8'
    source configurations.doc.collect { zipTree(it) }
    options.memberLevel = JavadocMemberLevel.PRIVATE
    include '**/*.java'
    options.addStringOption('Xdoclint:none', '-quiet')
}

将api模块(如果么有api模块,在总项目目录下配置即可)中的 build.gradle 文件增加如上内容。

增加之后, 进入该模块目录,运行 gradle javadoc 看下是否可以生成javadoc文件

image

3.4 将生成文档的步骤自动化起来

上面的步骤都完成后,我们就开始想办法把生成文档的步骤自动化起来:

在项目根目录下增加 .gitlab-ci.yml, 内容如下:

stages:
- gendoc
 
genJavaDoc:
  stage: gendoc
  script:
  - "ls -al"
  - "export APPID=appid_xx" # appid_xx 唯一标示 可以修改成自己的
  - "export API_PATH=api_modle" # api_modle api模块的路径 根据自己项目填写
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # No need to change the following Env Variables
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  - "export ROOT_FOLDER=/data/javadoc" #javadoc的根目录
  - "export PROJECT_FOLDER=$ROOT_FOLDER/$APPID" # 项目目录
  - "export CURRENT_GIT_BRANCH=$CI_BUILD_REF_NAME" # 当前分支
  - "export TARGET_DOC_FOLDER=$PROJECT_FOLDER/${CURRENT_GIT_BRANCH//\\//-}" # 文档目录 我把分支中的/替换成了-
  - "cd $API_PATH" # 进入api模块的路径
  - "gradle clean javadoc" # 运行 gradle clean javadoc 生成文档
  - "mkdir -p $TARGET_DOC_FOLDER"
  - "rm -rf $TARGET_DOC_FOLDER/*"
  - "mv ./build/docs/javadoc/* $TARGET_DOC_FOLDER" # 把生成出来的文档移动到对应的文档目录
 
  tags:
  - gc-java-doc

配置完这些, 基本可以做到 对这个项目push代码, 然后会在服务器对应的目录下生成文档了,但是还不能被查看到

3.5 配置nginx文件服务

server {
    listen       80;
 
    autoindex on; # 开启文件目录索引
    autoindex_exact_size on; # 展示文件大小
    autoindex_localtime on; # 展示文件时间
 
    location / {
        root   /data/javadoc/; # 目录
    }
}

现在的页面应该长这个样子

image

3.6 构建一个索引界面

我们做个页面优化下:

在doc根目录 /data/javadoc 下面建立 index.html.template 这个文件, 内容如下

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <!-- import CSS -->
        <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    </head>
    <body>
        <div id="app">
            <el-container>
                <el-header class="doc-header" height="150px">
                    <div class="header-title">
                        店铺信息组JavaDoc
                    </div>
                    <div class="header-desc">
                    </div>
                </el-header>
                <div class="spilt-line"></div>
                <el-container>
                    <div class="appid">
                        <el-aside width="300px">
                            <el-menu :default-active="activityIndex" class="el-menu-vertical-demo" v-for="(item, index) in file_dir" @select="handleSelect">
                                <el-menu-item :index="index+''">
                                    <i class="el-icon-menu"></i>
                                    <span slot="title"></span>
                                </el-menu-item>
                            </el-menu>
                        </el-aside>
                    </div>
                    <div class="appid_branch">
                        <el-main>
                            <el-table :data="activity_appid_branch_info" style="width: 100%">
                                <el-table-column label="分支" >
                                    <template class="branc_name" slot-scope="scope">
                                        <a :href="activity_appid_info+'/'+scope.row.name"></a>
                                    </template>
                                </el-table-column>
                            </el-table>
                        </el-main>
                    </div>
                </el-container>
            </el-container>
        </div>
    </body>
    <!-- import Vue before Element -->
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <!-- import JavaScript -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: function() {
                return {
                    file_dir: GC_JAVA_DOC_FILE_DIR,
                    activityIndex: '0'
                }
            },
            computed: {
                activity_appid_branch_info: function () {
                    console.log(this.activityIndex)
                    return this.file_dir[parseInt(this.activityIndex)]['branch']
                },
                activity_appid_info: function() {
                    console.log(this.activityIndex)
                    return this.file_dir[parseInt(this.activityIndex)]['appid']
                }
            },
            methods: {
                handleSelect(key, keyPath) {
                    this.activityIndex = key+''
                }
            }
        })
    </script>
    <style scop>
        #app {
            width: 100%;
        }
        .spilt-line {
            height: 1px;
            width: 100%;
            background-color: #e6e6e6;
            margin: 0px 0px 30px 0px;
        }
        .doc-header {
            margin: 10px;
            position: relative;
        }
        .header-title {
            text-align: center;
            font-size: 30px;
        }
        .header-desc {
            font-size: 15px;
            position: absolute;
            bottom: 0px;
        }
        .appid_branch {
            width: 100%;
            text-align: center;
        }
        .branc_name {
            width: 100%;
            text-align: center;
        }
        el-table-column {
            font-size: 100px;
            width: 100%;
            text-align: center;
        }
    </style>
</html>

再加一个replace_template.py文件, 内容如下:

#! -*- coding:utf8 -*-
import sys
import os
import json
 
reload(sys)
sys.setdefaultencoding("utf-8")
 
def replace(file_dir):
    index_template_file = open("index.html.template", "r")
    index_file = open("index.html", "w+")
    print 'relace:', file_dir
    while True:
        line = index_template_file.readline()
        if not line or len(line) < 1:
            break
 
        line = line.replace("GC_JAVA_DOC_FILE_DIR", json.dumps(file_dir))
        index_file.write(line)
 
    index_file.flush()
 
def list_dir(root_path):
    '''
        build dir struct
    '''
    result = []
    file_names = os.listdir(root_path)
    for file_name in file_names:
        file_path = root_path + '/' + file_name
        if os.path.isdir(file_path):
            one_info = {}
            one_info['appid'] = file_name
            one_info['branch'] = []
            child_file_names = os.listdir(file_path)
            for child_file_name in child_file_names:
                one_info['branch'].append({"name": child_file_name})
 
            result.append(one_info)
 
    return result
 
if __name__ == "__main__":
    file_dir = list_dir('.')
    replace(file_dir)

然后修改 .gitlab-ci.yml

stages:
- gendoc
 
genJavaDoc:
  stage: gendoc
  script:
  - "ls -al"
  - "export APPID=appid_xx" # appid_xx 唯一标示 可以修改成自己的
  - "export API_PATH=api_modle" # api_modle api模块的路径 根据自己项目填写
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # No need to change the following Env Variables
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  - "export ROOT_FOLDER=/data/javadoc" #javadoc的根目录
  - "export PROJECT_FOLDER=$ROOT_FOLDER/$APPID" # 项目目录
  - "export CURRENT_GIT_BRANCH=$CI_BUILD_REF_NAME" # 当前分支
  - "export TARGET_DOC_FOLDER=$PROJECT_FOLDER/${CURRENT_GIT_BRANCH//\\//-}" # 文档目录 我把分支中的/替换成了-
  - "cd $API_PATH" # 进入api模块的路径
  - "gradle clean javadoc" # 运行 gradle clean javadoc 生成文档
  - "mkdir -p $TARGET_DOC_FOLDER"
  - "rm -rf $TARGET_DOC_FOLDER/*"
  - "mv ./build/docs/javadoc/* $TARGET_DOC_FOLDER" # 把生成出来的文档移动到对应的文档目录
  - "cd $ROOT_FOLDER" # 进入项目根目录
  - "python replace_template.py" # 运行这个python脚本
 
  tags:
  - gc-java-doc

好大功告成,看下面的结果

4. 结果

4.1 push代码

image

4.2 触发pipline

image

4.3 查看文档

image