一、引言
随着AI技术的发展,AI技术在各种领域已经开始形成新的生产力,包括问答知识库,语音生成,图片生成等,现在一个新的AI赛道也已经初具规模,那就是AI代码助手,AI代码助手通过丰富的知识库以及优秀的训练模型,可以在程序开发过程中提供有效的帮助,甚至可以在完全不熟悉的语言中,通过AI代码助手的帮助实现需要的功能,今天这篇文章将通过一个实际的使用案例,介绍腾讯云AI代码助手。
二、开发环境介绍
开发语言使用go,一个我并熟悉的语言,采用go语言的主要原因是我需要开发的工具本身很简单,并不需要太多的工具包,其次我需要的我的工具尽量不依赖外部文件,比如Java的jdk,Python的运行环境,go可以直接编译为二进制文件去执行,最后一个原因是有腾讯云AI代码助手,在其帮助下,使用自己不熟悉的语言实现我想要的功能,应该也不是什么困难的事。开发工具使用goland,并且已经安装好腾讯AI代码助手,安装方式可以参考官方文档。
三、腾讯云AI代码助手使用实践
3.1、开发工具的目的
目前所有项目都在使用docker部署,每次上线/更新、都需要使用docker打包镜像,更新应用等,但是公司的情况是开发百分之九十以上并不会编写dockerfile以及使用docker来更新应用(删除以及启动新应用),之前我曾编写过一个shell脚本,通过shell脚本实现部署更新功能,以下是对功能的简要说明;
1、通过jar包名称,获取应用名称以及版本号,作为docker镜像的名称以及tag,docker容器名称也使用应用名称
2、通过shell脚本的配置项,指定java运行的基础镜像版本
3、通过shell配置项,设置java运行内存
4、通过shell配置项,设置容器运行命令
5、生成dockerfile并自动打包镜像
6、生成start.sh,保存容器启动命令
但是随着项目的增多,不确定开始增加,比如说有的项目运行参数需要使用configServer,有的需要使用nacos,jdk版本的不断更新等等问题,脚本已经难以满足需求,而且所有应用的运行参数是开发提供,更新操作是现场运维完成,沟通中难免存在问题。所以迫切的需要一个新的工具来解决。
3.2、设计思路
既然所有运行参数由开发提供,那么就约定一个yaml文件,在此文件中定义所有开发提供的信息,这样就避免了口口相传,必出歧义的问题,我们只需要读取yaml文件内容,并解析成Dockerfile,生成镜像,并生成docker运行命令即可,后续现场运维则不必关注应用具体运行参数,开发也不必关注容器镜像生成的步骤。开发语言决定使用golang,直接编译成二进制文件在服务器运行。
3.3、开发过程
1、首先设计出我们需要的yaml格式
代码语言:javascript复制# 用于生成Dockerfile和start.sh
# base-image: 基础镜像信息,这里未给出具体镜像名、标签等信息
# cmd: 容器启动时要执行的命令列表
# name: 容器名称,这里未给出具体名称
# version: 版本信息,这里未给出具体版本
# network-mode: 网络模式,这里未给出具体模式,如 bridge、host 等
# restart-policy: 重启策略,这里未给出具体策略,如 always、on-failure 等
# logs-opts: 日志选项,这里配置了日志的最大大小和文件数量
# - max-size: 日志文件的最大大小,这里未给出具体值,如 10m
# - max-file: 日志文件的最大数量,这里未给出具体值,如 3
# envs: 环境变量,这里设置了时区为 Asia/Shanghai
# volumes: 卷挂载信息,这里未给出具体的源路径和目标路径
base-image:
cmd:
name:
version:
network-mode:
restart-policy:
logs-opts:
- max-size:
- max-file:
envs:
- TZ: Asia/Shanghai
volumes:
- source:
target:
我们利用腾讯AI代码助手,为这个文件生成注释
2、创建一个struck绑定Yaml文件
代码语言:javascript复制type deployYamlConfig struct {
BaseImage string `yaml:"base-image"`
Cmd string `yaml:"cmd"`
Name string `yaml:"name"`
Version string `yaml:"version"`
NetworkMode string `yaml:"network-mode"`
RestartPolicy string `yaml:"restart-policy"`
LogsOpts []map[string]string `yaml:"logs-opts"`
Envs []map[string]string `yaml:"envs"`
Volumes []map[string]string `yaml:"volumes"`
}
这个过程中腾讯AI代码助手会自动帮助我们推理后面的内容
3、通过解析yaml文件内容和获取当前路径下jar文件名称,生成Dockerfile
首先获取jar文件名称
代码语言:javascript复制func getJarName() (jarName string, err error) {
dir := "." // 获取当前工作目录
jars, err := filepath.Glob(filepath.Join(dir, "*.jar"))
if err != nil {
fmt.Println("Error finding jar files:", err)
return
}
if len(jars) == 1 {
return jars[0], nil
} else if len(jars) > 1 {
return "", errors.New("没有找到jar文件")
} else {
fmt.Println("没有找到jar文件")
return "", errors.New("没有找到jar文件")
}
}
这里使用腾讯AI代码助手帮忙,我提出的问题是"go如何获取当前路径下的jar文件名称,并判断是否只有一个jar文件",如果生成的答案不太理想,我们可以点击重新生成。
解析yaml文件
代码语言:javascript复制func yamlUnmarshal(yamlFile string) config.DeployYamlConfig {
data, err := ioutil.ReadFile(yamlFile)
var deployYamlConfig config.DeployYamlConfig
err = yaml.Unmarshal(data, &deployYamlConfig)
if err != nil {
fmt.Println("Error unmarshalling yaml:", err)
}
return deployYamlConfig
}
然后我们生成Dockerfile
代码语言:javascript复制func generateDockerfile(jarName string,deployYamlConfig config.DeployYamlConfig) string{
var Dockerfile string
Dockerfile = fmt.Sprintf("FROM %sn", deployYamlConfig.BaseImage)
Dockerfile = fmt.Sprintf("COPY %s /%sn", jarName,jarName)
Dockerfile = fmt.Sprintf("CMD %sn", deployYamlConfig.Cmd)
return Dockerfile
}
生成build镜像命令
代码语言:javascript复制func generateBulidCmd(jdeployYamlConfig config.DeployYamlConfig) string {
var buildCmd string
buildCmd = fmt.Sprintf("docker build -t %s .", jdeployYamlConfig.Name)
return buildCmd
}
同样的以上两个方法在腾讯AI代码助手的推理下,快速完成
最后我们再写一个将String保存为文件的方法,因为我们不止要保存一个Dockerfile,还需要保存容器运行命令以及镜像的build命令
代码语言:javascript复制func saveToFile(content string, fileName string ,fileMode int) {
file, err := os.OpenFile(fileName,os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(fileMode))
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
fmt.Println("Error closing file:", err)
}
}(file)
_, err = file.WriteString(content)
if err != nil {
fmt.Println("Error writing to file:", err)
return
}
}
这部分内容结合提问和推理两个功能实现代码,以下是提问部分给出的示例,我们通过对示例的修改加上推理的功能顺利完成。
4、最后生成我们的容器运行脚本
代码语言:javascript复制func generateStartContainer(deployYamlConfig config.DeployYamlConfig) string {
var startContainer string
startContainer = fmt.Sprintf("docker run -d --name %s --network %s --restart %s %s ", deployYamlConfig.Name, deployYamlConfig.NetworkMode, deployYamlConfig.RestartPolicy, deployYamlConfig.Name)
return startContainer
}
自动推理 简单修改后就是我们想要的代码
5、 在main方法中将所有流程连起来,使用go build打包
代码语言:javascript复制func main() {
deployYamlConfig := tools.YamlUnmarshal("./deploy.yaml")
JarName, _ := tools.GetJarName()
tools.SaveToFile(tools.GenerateDockerfile(JarName, deployYamlConfig), "/opt/app/Dockerfile/Dockerfile", 0644)
tools.SaveToFile(tools.GenerateBuildCmd(deployYamlConfig), "/opt/app/Dockerfile/build.sh", 0755)
tools.SaveToFile(tools.GenerateStartContainer(deployYamlConfig), "/opt/app/bin/start.sh", 0755)
}
不仅仅能推理出代码内容,连我所需的文件权限都完全正确,我们只需要修改需要保存的路径即可
6、测试
将update复制到linux服务器,编写一个测试用的deploy.yaml
代码语言:javascript复制cp update /usr/local/bin/
chmod 755 /usr/local/bin/update
mkdir test
cd test
vim deploy.yaml
# 内容如下
base-image: openjdk:8
cmd: java -version
name: aaa
version: aaa
network-mode: host
restart-policy: always
logs-opts:
- max-size: 10m
- max-file: 1
envs:
- TZ: Asia/Shanghai
volumes:
- source: /opt/app/data
target: /opt/app/data
# 创建一个假jar文件
touch a.jar
# 执行update
update
update执行完之后查看有没有正常生成我们所需的Dockerfile,build.sh和start.sh,测试build.sh和start.sh能否正常执行
build.sh执行效果
start.sh执行效果
四、获得帮助与提升
腾讯AI代码助手切实的能帮助我快速上手一个不熟悉的语言,并实现一些功能。通过腾讯AI代码的使用,使我快速了解了go的基础语法,并能在编码时提供有效的帮助,例如通过补全注释功能,能够快速为代码添加简洁易懂的注释;通过推理功能能快速补全我所需代码;通过问答功能能给出我所需的代码示例;作为一个开发者,只需要在现有基础上简单修改,就可以完成想要的功能。
五、建议
1、逻辑推理功能会给出一些不太合乎逻辑的代码
推理功能这块估计还得好好优化一下子,就这次体验来说大多时候是可以正常推理出我所需要的代码逻辑,但是代码中的具体内容确实也是存在一些错误的,比如给出一些不存在的方法或者已经废弃的方法;还有很少的一部分时候给出的代码和我想要的代码几乎没有任何关系。
2、逻辑推理的代码如果过长不会自动折行
3、问答会出现异常中断,并未完整回答问题的情况(提问问题"如何切换路径")
这个地方应该就是一个bug了,但是触发bug的机制我还没分析出来,希望能尽快更新版本修复此bug吧
六、结语
其实整篇文章尚未完成所有功能,比如build.sh和start.sh自动执行,yaml文件中环境变量和持久化路径的解析以及生成到start.sh中,因为剩余的内容是在是还有点多,所以决定先完成整体的运行逻辑,之后在去补充细节内容,一篇文章实在是很难把所有东西全部写进去,算是有一些小遗憾吧。
评价一下腾讯AI代码助手,所有提供的功能都非常的实用,代码生成速度以及问题回答速度都非常快,存在一些小问题,后续应该也都会慢慢恢复。通过工具的使用确实能大大降低开发难度,提升代码编写效率。
最后,希望能一直免费使用!!!
七、续(2024/08/17)
由于时间的原因,前面内容并没有完全实现我们所需的功能,比如完整的部署目录规划,脚本的自动执行,以及生成的start.sh格式不正确,内容不完整等问题。所以这部分补充内容继续使用腾讯云AI代码助手来完善我们的工具
7.1、完善未完成的功能
1、规划完整应用部署路径,使整个部署过程更加规范
2、完善镜像构建过程
3、完善容器启动过程
7.2、设计思路
1、规划出完整的部署路径如下
代码语言:javascript复制/opt/appName/{bin conf data dockrfile logs update}
opt: 部署基础路径,定义到yaml中可调整
appName:从部署路径中获取
bin:存放启动容器的start.sh
conf: 存放需要持久化存储的配置文件
data: 存放需要持久化存储的数据
dockerfile:存放各历史版本的构建文件
logs: 存放需要持久化的日志
update: 存放deploy.yaml以及每次部署更新使用的文件
此处需要增加功能,自动创建目录
2、完善镜像构建过程
之前实现的功能需要手动执行build.sh以及复制应用jar包。我们需要调整为自动
3、完善容器启动过程
之前实现的功能部分run命令无法正常解析写入start.sh,此处需要分析为什么没能正常写入到start.sh中,并自动执行脚本
7.3、开发过程
1、首先调整我们的yaml文件,增加部署基础部署路径/opt
代码语言:javascript复制base-image:
base-dir:
cmd:
name:
version:
network-mode:
restart-policy:
logs-opts:
- max-size:
- max-file:
envs:
- TZ: Asia/Shanghai
volumes:
- source:
target:
调整结构体增加BaseDir
代码语言:javascript复制type DeployYamlConfig struct {
BaseImage string `yaml:"base-image"`
BaseDir string `yaml:"base-dir"`
Cmd string `yaml:"cmd"`
Name string `yaml:"name"`
Version string `yaml:"version"`
NetworkMode string `yaml:"network-mode"`
RestartPolicy string `yaml:"restart-policy"`
LogsOpts []map[string]string `yaml:"logs-opts"`
Envs []map[string]string `yaml:"envs"`
Volumes []map[string]string `yaml:"volumes"`
}
我们需要一个方法,帮我们拼接所有路径并进行校验,如果不存在则自动创建,老惯例我们使用腾讯云AI代码助手帮助我们完成部分功能
代码语言:javascript复制func DefinitionCheckDir(deployYamlConfig config.DeployYamlConfig) map[string]string {
m := make(map[string]string)
appDir := deployYamlConfig.BaseDir "/" deployYamlConfig.Name
binDir := appDir "/bin"
dockerFileDir := appDir "/dockerfile" "/" deployYamlConfig.Version
logDir := appDir "/logs"
dataDir := appDir "/data"
m["appDir"] = appDir
m["binDir"] = binDir
m["dockerFileDir"] = dockerFileDir
m["logDir"] = logDir
m["dataDir"] = dataDir
return m
}
使用腾讯云AI代码助手,帮我们生成一个检查方法
文件检查方法
代码语言:javascript复制func CheckAndCreateDirs(dirPath string) error {
// 检查目录是否存在
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
// 如果不存在,则创建目录
if err := os.MkdirAll(dirPath, 0755); err != nil {
return err // 返回错误
}
}
return nil // 返回nil表示成功
}
在main.go中增加方法调用
s := tools.DefinitionCheckDir(deployYamlConfig)
for _, v := range s {
err := tools.CheckAndCreateDirs(v)
if err != nil {
return
}
}
修改原main.go中的文件生成参数
tools.SaveToFile(tools.GenerateDockerfile(JarName, deployYamlConfig), m["dockerFileDir"] "/Dockerfile", 0644)
tools.SaveToFile(tools.GenerateBuildCmd(deployYamlConfig), m["dockerFileDir"] "/build.sh", 0755)
tools.SaveToFile(tools.GenerateStartContainer(deployYamlConfig), m["binDir"] "/start.sh", 0755)
2、完善镜像构建过程
调整之前的方法,我们需要调用shell执行build过程,尽量不适用相对路径,避免调用时切换路径,之前生成的build.sh没有版本tag,这次也加上
代码语言:javascript复制# 原方法
func GenerateBuildCmd(deployYamlConfig config.DeployYamlConfig) string {
var buildCmd string
buildCmd = fmt.Sprintf("docker build -t %s .", deployYamlConfig.Name)
return buildCmd
}
# 修改后
func GenerateBuildCmd(deployYamlConfig config.DeployYamlConfig) string {
var buildCmd string
m := DefinitionCheckDir(deployYamlConfig)
// 生成build.sh
buildCmd = fmt.Sprintf("#!/bin/bashn")
buildCmd = fmt.Sprintf("docker build -t %s:%s %s", deployYamlConfig.Name , deployYamlConfig.Version, m["dockerFileDir"])
return buildCmd
}
接下来腾讯云AI代码助手登场,协助我们处理一下如何使用go在linux系统中调用shell命令
RunBuild 调用shell, 执行build.sh
代码语言:javascript复制func RunBuild(jarName string, deployYamlConfig config.DeployYamlConfig) {
m := DefinitionCheckDir(deployYamlConfig)
mvJarfile := exec.Command("mv", m["appDir"] "/update/" jarName, m["dockerFileDir"] "/" jarName)
build := exec.Command(m["dockerFileDir"] "/build.sh")
_, err := mvJarfile.CombinedOutput()
if err != nil {
return
}
output, err := build.CombinedOutput()
if err != nil {
fmt.Println("Error:", err)
}
fmt.Printf("命令输出:n%sn", string(output))
}
在mian.go中添加方法的执行
代码语言:javascript复制 tools.RunBuild(JarName, deployYamlConfig)
3、完善容器启动过程
修改原来的方法,修正丢失环境变量配置和数据持久化配置
根据腾讯云AI助手的提示,简单进行修改
代码语言:javascript复制# 原方法
func GenerateStartContainer(deployYamlConfig config.DeployYamlConfig) string {
var startContainer string
startContainer = fmt.Sprintf("docker run -d --name %s --network %s --restart %s %s ", deployYamlConfig.Name, deployYamlConfig.NetworkMode, deployYamlConfig.RestartPolicy, deployYamlConfig.Name)
return startContainer
}
# 修改后
func GenerateStartContainer(deployYamlConfig config.DeployYamlConfig) string {
var startContainer string
startContainer = fmt.Sprintf("#!/bin/bashn")
startContainer = fmt.Sprintf("docker run -d --name %s --network %s --restart %s ", deployYamlConfig.Name, deployYamlConfig.NetworkMode, deployYamlConfig.RestartPolicy)
for _, v := range deployYamlConfig.Volumes {
startContainer = fmt.Sprintf("-v %s:%s ", v.Source, v.Target)
}
for _, v := range deployYamlConfig.Envs {
for k, v := range v {
startContainer = fmt.Sprintf("-e %s=%s ", k, v)
}
}
for _, v := range deployYamlConfig.LogsOpts {
for k, v := range v {
startContainer = fmt.Sprintf("--log-opt %s=%s ", k, v)
}
}
startContainer = fmt.Sprintf("%s:%s", deployYamlConfig.Name, deployYamlConfig.Version)
return startContainer
}
增加自动运行start.sh
代码语言:javascript复制func RunStart(deployYamlConfig config.DeployYamlConfig) {
m := DefinitionCheckDir(deployYamlConfig)
start := exec.Command(m["binDir"] "/start.sh")
output, err := start.CombinedOutput()
if err != nil {
fmt.Println("Error:", err)
}
fmt.Printf("命令输出:n%sn", string(output))
}
# main.do添加
tools.RunStart(deployYamlConfig)
注:yaml的结构体进行了如下调整
代码语言:javascript复制package config
type DeployYamlConfig struct {
BaseImage string `yaml:"base-image"`
BaseDir string `yaml:"base-dir"`
Cmd string `yaml:"cmd"`
Name string `yaml:"name"`
Version string `yaml:"version"`
NetworkMode string `yaml:"network-mode"`
RestartPolicy string `yaml:"restart-policy"`
LogsOpts []map[string]string `yaml:"logs-opts"`
Envs []map[string]string `yaml:"envs"`
Volumes []Volume `yaml:"volumes"`
}
type Volume struct {
Source string `yaml:"source"`
Target string `yaml:"target"`
}