📚目录
- 📚简介:
- ⚙️ 所需工具:
- 💨内网穿透配置
- 💭工具介绍
- ✨命令安装
- 🎊配置Cpolar
- 🕳️关闭防火墙
- 🥛防火墙端口放行规则(关闭防火墙可以忽略)
- 🍬小章总结
- 🚀项目创建提交Giee
- 🏡idea创建工程
- ✍️编写代码
- 💭城市天气获取接口
- 💫主要代码
- 💨运行项目
- 💨接口测试
- 🚀推送代码到远程
- 🍬小章总结
- 🎄CI/CD配置使用
- 🎃创建CI/CD流水线
- 🌈流水线代码
- 🖥成功一半
- 📃优化流水线配置
- ⚡自动化效果
- ⛄程序结合内网穿透
- 🌈启动Cpolar
- 🌠启动java程序
- 🌄内网穿透接口测试
- 🎉总结:
📚简介:
使用gitee来存储代码,我们修改或者增加新功能时,就不需要把本地的jar编译好上传服务器,直接使用CI/CD来完成部署,里面已经支持Maven的构建与上传到远程服务器上完成项目的部署。这一次的记录,不需要你们买服务器,虽说服务器新用户一年也就八九十块钱,但是我们也可以不花钱学会,因为我打算使用本地电脑虚拟机新建一个Linux,然后搭配免费的内外穿透工具来完成本次的教学.
⚙️ 所需工具:
本地Linux
:本次教学不涉及虚拟机Linux的安装如需购买可以看往期教程购买一台服务器安装 青龙面版 撸京豆gitee的账号
:去注册gitee账号映射工具(cpolar)
:下载Linux的版本
💨内网穿透配置
💭工具介绍
Cpolar官网下载,Linux版本
我们去下载Linux版本
可以看官网已经列举出来Linux的安装步骤
✨命令安装
我这使用Xshell连接本地虚拟机中的Linux
输入下载代码使用Curl方式
curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash
查看版本号
cpolar version
🎊配置Cpolar
登录cpolar官网后台,点击左侧的验证,查看自己的认证token,之后将token贴在命令行里
cpolar authtoken 自己的Authtoken
设置Cpolar开机自启
sudo systemctl enable cpolar
现在我们启动Cpolar,实现内网穿透,我们先开启一个http的8080端口
cpolar http 8080
如果我们使用接口软件来访问该地址,我们正好有java的8080端口程序在运行,则会接收到请求信息。(前提是本地Linux开启了对应端口号的防火墙)
注意:ctrl+c是退出Cpolar,每一次退出启动Cpolar的域名都会变
Cpolar的后台也可以看到对应的url地址
🕳️关闭防火墙
防火墙设置
service iptables status 查看防火墙状态
service iptables stop 关闭防火墙
service iptables start 启动防火墙
防火墙状态是启动的
我们需要关闭防火墙
🥛防火墙端口放行规则(关闭防火墙可以忽略)
查看防火墙放行的端口号(上面直接关闭防火墙可以不需要以下操作)
iptables -L -n
可以看出当前并没有放行8080的防火墙端口号,我们需要放行端口才能通过映射访问本地服务器
Linux 放行防火墙端口号,用于外界访问
## 开启
iptables -A INPUT -p tcp --dport 端口号 -j ACCEPT
## 保存配置
service iptables save
## 重启
service iptables restart
🍬小章总结
到这里内网穿透的基本就配置完成了,由于开启了Cpolar后Xshell就不能输入命令行,所以你们可以在开一个新的选项卡。
由于我们还没有写java程序,所以访问Cpolar的地址显示没有运行网站服务器,后续我们把代码上到本地服务器来看效果就直观了
🚀项目创建提交Giee
创建好Gitee账号后,我们点击右上角的加号进行新建仓库
创建自己的仓库,仓库名称使用英文
仓库创建好后,我们需要把仓库拉取到本地
我们在本地选择好文件夹目录,使用英文的目录,然后使用命令拉取下来,提示拉取一个空的仓库,我们到时候会把这个目录当做IDEA的开发目录,到时候代码就可以推送到远程上
该目录只有一个.git,里面存放着当前仓库git的一些配置信息
🏡idea创建工程
我们创建一个web服务我们先勾选,Spring web依赖,没有勾选也没事,到时候在pom.xml中添加依赖也行
创建好工程后,我们看下界面是否有git的图标,没有就去下载个git插件,这样我们就可以可视化的拉取代码,推送代码到远程分支,和切换分支等操作。
由于我这个版本所有捆绑git插件所以不需要下载,有关git的插件,如果你们的IDEA没有git插件可以去插件市场里面下载
git插件
直接去下载安装git插件,然后重启IDEA,这样插件才能生效,可以看往期文章 IDEA工具插件(持续更新中…)
红色文件的是没被git管理的文件,绿色的文件是添加到git管理的文件,这样我们提交推送远程的时候就可以把绿色文件推送到远程
选择好需要添加的文件,我们
右键选择添加到 VCS
✍️编写代码
把
application.properties
名称修改成application.yml
# 应用服务 WEB 访问端口
server:port: 8080
💭城市天气获取接口
至于写什么呢?要不用我以前写的,传递对应城市返回对应城市的天气消息,这样项目也不会那么无味,因为之前接口什么的已经分析好了,我直接请求接口就可以获取对应城市的天气信息,如果对如何分析天气接口感兴趣的可以前往往期文章【手把手教你】如何获取中国天气网,获取想要城市的天气-图文并茂-分析代码
💫主要代码
层级结构
- 控制层:用于接收请求,编写接口的类
- 响应返回对象
- 全局异常处理类,对发送错误的数据封装好回调给调用方
- 服务层:控制层调用服务层,服务层调用mapper层(持久层操作数据库的层),
因为我们这个不需要连接数据库所以省略,该类提供了传递城市返回城市天气的详细数据
ApiController 代码
/*** @Author itmei* @Date 2023/12/20 20:33* @description: 控制层* @Title: ApiController* @Package com.itmei.cicddemo.demos.controller*/
@RestController
@RequestMapping("/api")
public class ApiController {@Resourceprivate ApiService apiService;/**** @return*/@GetMapping("/cityWeatherDetails")public ResultData cityWeatherDetails(String city) {JSONObject realWeather = apiService.getRealWeather(city);return ResultData.success(realWeather);}
}
ResultData 代码
/*** @Author itmei* @Date 2022/4/10 11:38* @Version 1.0*/
public class ResultData extends HashMap<String, Object> {private static final long serialVersionUID = 1L;/*** 异常Code*/private static final Integer HTTP_STATUS_ERROR = 500;/*** 成功Code*/private static final Integer HTTP_STATUS_SUCCESS = 200;/*** 状态码*/public static final String CODE_TAG = "code";/*** 提示信息*/public static final String MSG_TAG = "msg";/*** 具体内容*/public static final String DATA_TAG = "data";/*** token*/public static final String TOKEN_TAG = "token";/*** 返回空数据*/public ResultData() {}/*** 初始化一个新创建的 ResultData 对象** @param code 状态码* @param msg 返回内容*/public ResultData(int code, String msg) {this.put(CODE_TAG, code);this.put(MSG_TAG, msg);}/*** 初始化一个新创建的 ResultData 对象** @param code 状态码* @param msg 返回内容* @param data 数据对象*/public ResultData(int code, String msg, Object data) {this.put(CODE_TAG, code);this.put(MSG_TAG, msg);/*** 判断是否有数据*/if (data != null && !"".equals(data)) {this.put(DATA_TAG, data);}}/*** 返回成功消息** @param code 状态码* @param msg 返回内容* @param data 数据对象* @return ResultData*/public static ResultData success(int code, String msg, Object data) {return new ResultData(code, msg, data);}/*** 返回成功消息** @param msg 成功提示信息* @param data 数据对象* @return*/public static ResultData success(String msg, Object data) {return new ResultData(HTTP_STATUS_SUCCESS, msg, data);}/*** 返回成功提示信息** @param msg 成功提示信息* @return*/public static ResultData success(String msg) {return ResultData.success(msg, null);}public static ResultData success(Object data) {return ResultData.success("操作成功" , data);}public static ResultData success() {return ResultData.success("操作成功");}/*** 错误提示** @return*/public static ResultData error() {return error(HTTP_STATUS_ERROR, "操作失败");}/*** 错误提示** @param msg 数据内容* @return*/public static ResultData error(String msg) {return error(HTTP_STATUS_ERROR, msg);}/*** 返回错误信息** @param msg 返回内容* @param data 数据对象* @return*/public static ResultData error(String msg, Object data) {return new ResultData(HTTP_STATUS_ERROR, msg, data);}/*** 返回错误消息** @param code* @param msg* @return*/public static ResultData error(int code, String msg) {return new ResultData(code, msg, null);}/*** 方便链式调用** @param key* @param value* @return*/@Overridepublic ResultData put(String key, Object value) {super.put(key, value);return this;}
}
GlobalExceptionHandler 代码
/*** @Author itmei* @Date 2023/12/20 20:58* @description: 全局异常捕获类* @Title: GlobalExceptionHandler* @Package com.itmei.cicddemo.demos.exception*/
@RestControllerAdvice
public class GlobalExceptionHandler {private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);/*** 全局异常*/@ExceptionHandler(Exception.class)public ResultData handleException(Exception e, HttpServletRequest request){String requestURI = request.getRequestURI();log.error("请求地址'{}',发生系统异常.", requestURI, e);return ResultData.error(e.getMessage());}
}
ApiService 代码
/*** @Author itmei* @Date 2023/12/20 20:34* @description: 服务层* @Title: ApiService* @Package com.itmei.cicddemo.demos.service*/
@Service
public class ApiService {private static Map<String,Long> cityAreaIdMap = new HashMap<>();/*** 项目启动后会调用该方法* 获取所有城市AreaId** @return*/@PostConstructpublic void getCity() {String cityJs = "https://j.i8tq.com/weather2020/search/city.js";//发送请求获取数据String cityStr = HttpUtil.createGet(cityJs).header("Referer", "http://www.weather.com.cn/").execute().body();//需要解析数据String cityJson = cityStr.replace("var city_data =", "");//创建集合存储数据//使用 NAMECN:AREAIDJSONObject entries = JSONUtil.parseObj(cityJson);Set<String> keys = entries.keySet();for (String key : keys) {//System.out.println("第一层 省" + key);Set<String> citys = entries.getJSONObject(key).keySet();for (String city : citys) {//System.out.println("--第二层 城市" + city);Set<String> areas = entries.getJSONObject(key).getJSONObject(city).keySet();for (String area : areas) {//System.out.println("---第三层 区:" + area);JSONObject jsonObject = entries.getJSONObject(key).getJSONObject(city).getJSONObject(area);//System.out.println(jsonObject.toString());cityAreaIdMap.put(jsonObject.getStr("NAMECN"), jsonObject.getLong("AREAID"));}}}}private Long cityAreaIdMap(String city) {Long areaId = cityAreaIdMap.get(city);if (ObjectUtil.isEmpty(areaId)) {throw new RuntimeException("查询 "+ city +" 城市异常,确认城市是否正确!");}return areaId;}/*** 获取实时城市天气** @param city* @return*/public JSONObject getRealWeather(String city) {//取出对应城市的areaidLong areaId = cityAreaIdMap(city);//实时天气的地址String REALWEATHER_URL = "http://d1.weather.com.cn/sk_2d/";//拼接请求地址String uri = REALWEATHER_URL + areaId + ".html";String res = HttpUtil.createGet(uri).timeout(10000).header("Referer", "http://www.weather.com.cn/").execute().body();//替换数据不然会影响json的解析String json = res.replace("var dataSK=", "");return JSONUtil.parseObj(json);}}
完整pom.xml的代码,用到的对应依赖等信息
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.itmei</groupId><artifactId>cicd-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>cicd-demo</name><description>用于演示gitee的CI/CD的Spring Boot项目,该项目的功能定义用于获取对应城市的天气消息等功能</description><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.6.13</spring-boot.version><hutool.version>5.8.0.M2</hutool.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--java常用工具--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><!--添加打包的名称--><finalName>cicd-weather</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.itmei.cicddemo.CicdDemoApplication</mainClass></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>
💨运行项目
启动成功后,打印程序端口号以及启动时间2.5秒
💨接口测试
使用你们自己熟悉的接口工具如ApiFox,ApiPost,PostMan等
请求接口:/api/cityWeatherDetails?city=福州
响应结果:
{"msg": "操作成功","code": 200,"data": {"nameen": "fuzhou","cityname": "福州","city": "101230101","temp": "8.3","tempf": "46.9","WD": "西北风","wde": "NW","WS": "2级","wse": "5km/h","SD": "93%","sd": "93%","qy": "1018","njd": "16km","time": "21:30","rain": "0","rain24h": "0","aqi": "20","aqi_pm25": "20","weather": "多云","weathere": "Cloudy","weathercode": "d01","limitnumber": "","date": "12月20日(星期三)"}
}
传递错误测试查看错误效果
响应结果:
{"msg": "查询 奥尔良 城市异常,确认城市是否正确!","code": 500
}
🚀推送代码到远程
现在确定代码没问题了,我们可以通过IDEA的可视化界面操作git,把代码推送gitee的仓库中了。
选择需要提交的文件,怎么选择多个文件呢,你按着Shift+文件就会被多选,然后我们右键点击提交文件
需要注意的是当前提交的是本地git仓库,远程现在还是没有代码的
点完这个推送后代码就会推送到远程gitee的仓库中
增加项目打jar包的名称,记得推送到远程哦
🍬小章总结
到这里我们就完成了代码的编写和git的代码推送。看到这里了给自己加加油吧,因为你也可以通过传递不同的城市,获取对应城市天气详情数据,动手敲起来,你一定可以得到收获的。
🎄CI/CD配置使用
🎃创建CI/CD流水线
在我们的仓库中选择,流水线
选择不自动创建,我们自己创建
我们自己点击创建流水线
把配置项添加进去,需要注意的是里面的
cicd-demo
你们都替换成GiteeGo应用名吧,然后需要注意的是./target/cicd-weather.jar
这个是git进行maven编译时需要帮他指定在那个目录下,不然找jar的位置
🌈流水线代码
version: '1.0'
name: master-pipeline
displayName: MasterCi
triggers:trigger: autopush:branches:include:- master
stages:- name: compiledisplayName: 编译strategy: naturallytrigger: autosteps:- step: build@mavenname: build_mavendisplayName: Maven 构建jdkVersion: 8mavenVersion: 3.3.9commands:- '# mvn -B clean package -Dmaven.test.skip=true'- ''- mvn clean package -Dmaven.test.skip=true -U -e -X -Bartifacts:- name: BUILD_ARTIFACTpath:- ./target/cicd-weather.jarcaches:- ~/.m2strategy: {}- step: publish@general_artifactsname: publish_general_artifactsdisplayName: 上传制品dependArtifact: BUILD_ARTIFACTartifactName: cicd-demostrategy: {}dependsOn: build_maven- name: releasedisplayName: 发布strategy: naturallytrigger: autosteps:- step: deploy@agentname: deploy_agentdisplayName: 主机部署hostGroupID:ID: 192.168.0.45hostID:- 76f3deployArtifact:- name: cicd-demotarget: ~/gitee_go/deploysource: builddependArtifact: BUILD_ARTIFACTscript:- '# 功能:部署脚本会在部署主机组的每台机器上执行'- '# 修改APP_NAME为GiteeGo应用名'- APP_NAME=cicd-demo- 'APP_HOME_DIR=/home/giteeAuto/giteeJar/${APP_NAME} # 存放jar包放到这个目录下'- 'APP_HOME_BACK_DIR=/home/giteeAuto/giteeJar/${APP_NAME}/back # 把之前存在的jar备份到该目录下'- 'APP_HOME_DIR_TARGET=${APP_HOME_DIR}/ #解压后的包名称需要提取里面的jar'- 'APP_HOME_DIR_TARGET_NAME=cicd-weather #解压后的包名称'- ''- mkdir -p ${APP_HOME_DIR}- mkdir -p ${APP_HOME_BACK_DIR}- ''- ''- 'JAR_NAME=${APP_HOME_DIR}/${APP_HOME_DIR_TARGET_NAME}.jar # jar包的路径名字'- 'JAR_BACK_NAME=${APP_HOME_BACK_DIR}/${APP_HOME_DIR_TARGET_NAME}.jar # 备份jar包的路径名字'- ''- ''- echo 'Gitee自动化开始运行 备份之前jar包'- ''- if [ -f ${JAR_NAME} ];then- echo "文件存在则移动文件到备份中"- mv ${JAR_NAME} ${JAR_BACK_NAME}_`date '+%Y%m%d_%H.%M.%S'`- else- echo "文件不存在忽略"- fi- ''- echo 'Gitee自动化开始运行 解压到指定目录中'- tar zxvf ~/gitee_go/deploy/${APP_NAME}.tar.gz -C ${APP_HOME_DIR}- echo '-----解压完成----'- ''- cd ${APP_HOME_DIR}/- ./deploy.sh restart- ''- echo '-----项目重启完成----'- ''notify: []strategy:retry: '0'
需要改动的地方
添加Linux主机
复制下来,粘贴到我们本地内网穿透的服务器命令行中
回到gitee的页面,可以看到我们本地的电脑已经在线了,这个时候在去配置我们的流水线
点击保存,并确认创建流水线
我们可以看到,创建后自动给我们执行了流水线并且完成了Maven的编译,和成品的上传
但是在发布到我们本地服务时发生了异常,出现问题不可怕,可怕的是今天写的有点晚了,都快11点了,保命要紧今天就先写到这,明天再出来解决错误问题。
错误提示没有wget命令导致的,我们回到本地Linux 命令行查看是否也提示没有这个命令
可以看出的确没有这个命令,提示-bash: wget: command not found
,那我们我们要安装wget
命令
使用yum 命令进行安装
yum -y install wget
可以看到又出现其他问题了,你们没有出现的可以忽略,这个是因为我的虚拟磁盘已经满了,需要清理下
## 查看根目录下的所有大于100M的文件并输出
find / -size +100M -type f -exec ls -l {} \;
rm -
然后cd 到需要输出的文件下使用rm命令进行删除,可别再根目录使用
rm -rf *
删除哦不然你就完犊子了,保守一点你们还是用rm -rf 文件名称
进行删除,删除完成后/dev/sda3 释放了2.3g的内存,我们接着使用命令安装wget
安装成功
wget
后的日志输出
回到流水线,我们点击失败的发布,然后点击重新执行,看看效果
🖥成功一半
其实到这一步了,并不是真正意义上的失败,因为我们还有一个启动程序Linux的sh脚本没有上传到本地服务器上,但是对应流水线来说的确是失败的,因为没有./deploy.sh 文件
现在我们在看下我们本地的linux服务是不是创建了对应的文件,和把对应的jar包解压到对应的位置上
本地服务器已经有正常的文件了,这一步是流水线自动把打包好的jar包拉取到本地,然后按照我们的linux命令进行执行的,是不是就很方便了。
📃优化流水线配置
因为这个流水线配置是比较早的版本,当时是有优化了下这个流水线配置,直接把运行的脚本添加到项目中,然后通过我们设置的命令运行启动脚本,我现在才想起来,所以就不去动之前的流水线配置了,不然有些读者可能会看懵了,配置和流水线输出的结果不一致,产生迷惑。
我们在代码中新建bin目录并且把运行脚本添加进去,创建的bin目录是和src同级的,不要搞错了,还有记得修改脚本中的运行jar名称
我们先更新远程的代码,本次拉取远程的代码是流水线的配置文件会拉到我们的工程中.
该目录下的存放着我们的流水线配置,我们可以直接在这上面修改流水线配置,我们把流水线配置下然后再一起推送到远程
添加三处位置
- ./bin/deploy.sh
- 'DEPLOY_NAME=deploy.sh #运行脚本文件'
- 'echo ''########## Gitee自动化开始运行 服务重启 ##########'''- cd ${APP_HOME_DIR}/- '#增加可执行权限'- chmod +x ${DEPLOY_NAME}- ./${DEPLOY_NAME} restart
提交git
由于我这个流水线设置的是有推送就会,自动运行流水线,完成代码的编译和发布
这里截图给你们看下代码配置,在git 的图形视图中的关系
⚡自动化效果
到这里,流水线的自动编译和发布远程,就完成了,但是我们严谨一点,我们看下本地的程序是否正常运行,边写文章边写代码,就没有顺利过,都要踩踩坑,把你们可能出现的问题都列举出来,并解决,这个问题是提示没有主类进行加载,
这一步问题出现在打包上
pom.xml 去除
<skip>true</skip>
,我已经把前面的pom.xml代码已经去除了,你们应该不会发生这个错误了
<skip>true</skip> 这个配置用来控制是否跳过该插件的执行,
默认值是 false,即不跳过。设置为 true 可以跳过 spring-boot-maven-plugin 插件的执行,
这意味着 Maven 不会对项目进行打包和构建,也不会生成可执行的 JAR 文件。
提交git自动执行流水线
见证奇迹的时候到了,运行命令查看jar的运行情况
非常好执行成功,接下来我们就配合Cpolar内网穿透实现接口的请求
⛄程序结合内网穿透
🌈启动Cpolar
由于开启了Cpolar后Xshell就不能输入命令行,所以你们可以在开一个新的选项卡
cpolar http 8080
🌠启动java程序
确保我们的java程序,正在运行中,然后再一个内网穿透来映射我们的本地java服务
🌄内网穿透接口测试
使用你们自己熟悉的接口工具如ApiFox,ApiPost,PostMan等
复制域名添加到接口请求工具中
请求地址:
http://66ae370d.r9.cpolar.top/api/cityWeatherDetails?city=福州
可以看出,内网穿透调用了我们的服务并且返回了数据给我们,这里使用内网穿透,所以会更慢一点点,因为要多走一个Cpolar的服务器,在转发到我们这个,但是问题也不大。
🎉总结:
终于在第三个晚上完成了这篇,巨多东西要总结,零零散散的知识点也多,不知不觉每个晚上都要写2~3个小时之间,现在距离我写总结已经是晚上10点半了,由于工作的原因,这一年写的博客没有去年的一半多,所以希望我写的文章可以帮助一些人,因为谁都是从不会到会的,依稀记得之前学校读书,当时小白的我虽说把代码写好了,本地IDEA也可以跑起来,但是网页就是打不开,我还在CSDN寻求帮助,可惜没有热度看到的人也微乎其微,当时我就下定决心,把最近学习的,可能帮助到读者的知识点写下来,也增强了知识点,学的东西多了,忘的也多了,回过头想想之前好像这个知识点我有总结,也可以回顾之前写的文章。今年2023年也到尾声了离2024年也就不到十几天的时间,希望新的一年可以多做些有意义的事情,大家一起加油🥳。