# project-builder
**Repository Path**: binarylei/project-builder
## Basic Information
- **Project Name**: project-builder
- **Description**: 基于当前最流行的技术栈编写的项目构建框架,用于项目急速构建及开发。
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 3
- **Created**: 2020-11-13
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# project-builder
#### 介绍
对常用开源技术封装后组建而成的一套项目构建基础框架,核心依赖为springboot并整合了springcloud等微服务开发常用技术,适用于快速搭建项目及开发。
#### 软件架构
组件库主要提供了以下组件及模块
| 组件名 | 功能 |
| --- | --- |
| project-builder-component-amqp | 消息队列集成及工具类,实现了从系统管理服务获取服务器地址配置。 |
| project-builder-component-consul | 服务注册组件,支持consul作为注册中心。 |
| project-builder-component-mybatisplus | 数据库操作组件,基于mybatisplus实现了双数据源切换。 |
| project-builder-component-remote | 远端服务的调用,已经集成了系统服务管理的字典、用户、附件、组织架构接口,引入该组件后程序里面可以直接调用。 |
| project-builder-component-web | 集成了springmvc提供了请求安全过滤、swagger、跨域等支持 |
| project-builder-component-zuul | 集成了zuul,实现了本地接口代理功能。 |
| project-builder-component-apm | 应用性能监控组件,封装了Elastic APM |
| project-builder-component-basedatacache | 通用缓存组件,封装了常用的基础数据缓存,支持自定义扩展。 |
|project-builder-component-config|配置管理组件,用于微服务自动注册及管理其程序配置。|
|project-builder-component-elasticsearch| 封装了集成elasticsearch的代码。|
|project-builder-component-excelparser| 封装了操作excel的代码,基于注解和poi实现。|
|project-builder-component-kafka| 封装了集成kafka的代码。|
|project-builder-component-logging| 日志管理组件,支持程序运行中动态调整日志级别|
|project-builder-component-memcache| 封装了操作memcache的代码。|
|project-builder-component-msgbus| 封装了操作消息队列的代码。|
|project-builder-component-nacos| 服务注册组件,支持nacos作为注册中心。|
|project-builder-component-nacos-config| 基于nacos实现的配置中心|
|project-builder-component-neo4j| 封装了操作neo4j的代码。|
|project-builder-component-redis | 封装了操作redis的代码,包含分布式锁及批量操作redis功能。|
|project-builder-component-swagger| 用于集成swagger。
|project-builder-component-syslog| 操作日志管理组件,用于记录应用操作日志。|
|project-builder-common| 通用的工具类、异常类、缓存类、接口返回类。|
|project-builder-component-parent| 项目的parent依赖|
|project-builder-dependencies| 组件库统一维护jar包依赖的模块|
#### 安装教程
1. git clone https://gitee.com/dhq/project-builder.git
2. mvn clean package
3. mvn deploy 项目代码到maven
4. 其他项目引入本项目发布后的依赖,具体使用方法见使用说明。
#### 使用说明
使用方式同springboot使用starter的方式非常接近,项目里面需要使用什么组件,直接在pom文件里面添加相关的依赖然后在配置文件里面添加相应的配置即可,可以自由的选择需使用的组件,以微服务注册组件的使用为例进行说明:
2.1引入依赖
```
<-- parent 部分-->
com.denghq
project-builder-component-parent
0.0.1-SNAPSHOT
<-- dependencies 部分-->
com.denghq
project-builder-component-consul
```
2.2 添加配置
```
spring:
profiles:
active: prod
application:
name: consul_test
cloud:
consul:
#微服务注册中心地址
host: 192.168.0.x
port: 8500
discovery:
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
service-name: ${spring.application.name}
health-check-interval: 10s
register: false
tags: xx服务,swagger=swagger-ui.html,application=${spring.application.name}
prefer-ip-address: true
healthCheckPath: /actuator/health
```
##### 组件说明
**消息总线组件开发文档**
如何使用?
一、项目中添加maven依赖
```
com.denghq
project-builder-component-msgbus
```
二、yml文件中添加配置
```
com:
denghq:
component:
msgbus:
rabbitmq: #目前只支持了rabbitmq,已经预留了接口后面再扩展kafaka
enable: true #是否启用rabbitmq消息总线,不设置不会加载rabbitmq消息总线
queueList: # queue声明队列,支持声明多个队列
- name: denghq.fmw.192.168.7.96:12001 #队列名称,规范: denghq.${服务标识}.${ip}:${port}
durable: true #是否持久化,含义同spring相关参数配置,默认:true
autoDelete: false #是否自动删除,含义同spring相关参数配置,默认:false
exchangeBinder: #声明交换机
- name: denghq.Exchange.TianjinEPPV5 #交换机名称
type: topic #交换机类型,含义同spring相关参数配置,默认:topic
durable: true #是否持久化,含义同spring相关参数配置,默认:true
autoDelete: true #是否自动删除,含义同spring相关参数配置,默认:false
bindings: #绑定路由键,用于消息监听,queueName:队列名称,routingKey:路由键
- { queueName: queueForMsgBus, routingKey: topic1.# }
- { queueName: queueForMsgBus, routingKey: topic2.# }
- { queueName: queueForMsgBus, routingKey: topic2 }
sender:
defaultExchange: exchangeForMsgBus # 消息默认发送到的交换机
topics: #配置某个主题的消息发送到的交换机,如果跟defaultExchange相同可省略,name:消息的主题,支持“#”通配符,exchange:交换机名称
- {name: topic1.#,exchange: exchangeForMsgBus}
- {name: topic2,exchange: exchangeForMsgBus}
msgReceiver:
maxMsgProcessThreadNum: 10 #调用消息处理启用的最大线程数,默认:10
maxConcurrentConsumers: 1 #消息处理最大消费者数量,默认:1
concurrentConsumers: 1 #消息处理最大消费者数量,默认:1
```
三、代码中使用
3.1 发送消息到主题(对于rabbitmq主题对应路由键)
方式一(推荐):
1> 代码中注入工具类rabbitMqMsgBusUtil。
```
@Autowired
private RabbitMqMsgBusUtil rabbitMqMsgBusUtil;
```
2> 构建规范格式的消息内容,发送消息。
```
@Test
public void testUtilSendMsg() {
//构建消息内容
List data = Lists.newArrayList();
data.add(new MsgDecoratorData(MqChangeTypeEnum.Insert.name(), "11111_demo"));
MsgDecorator.Builder msgBuilder = MsgDecorator.build();
MsgDecorator msg = msgBuilder
.setData(data)
.setDataType("TestRabbitMqMsgBusUtil send msg")
.setReportedTime(new Date())
.build();
//发送消息
rabbitMqMsgBusUtil.sendMsg("topic2", msg);
//发送消息,携带消息id(用于做消息发送确认,后面有介绍)
rabbitMqMsgBusUtil.sendMsg("topic2","msgId",msg);
}
```
方式二:
1> 直接注入消息总线
```
@Autowired
private RabbitMqMsgBus rabbitMqMsgBus;
```
2> 发送任意格式的消息
```
@Test
public void testSendMsg() {
//发送消息
rabbitMqMsgBus.publish("topic2xx", "111");
//发送消息,携带消息id(用于做消息发送确认)
rabbitMqMsgBus.publish("topic2xx", new SimpleMsg("----msg----", "111"));
}
```
3.2 消息确认机制(防消息发送丢失)
> 发送消息时携带msgId。
> spring中添加监听器,demo:
```
@Component
public class MsgSendEventHandlerDemo implements IMsgSendEventHandler {
/**
* 消息丢失
* @param event 消息事件
*/
@Override
public void onSuccessEvent(IMsgSendEvent event) {
System.out.println("发送成功(到交换机),msgId" + event.getMsgId());
//TODO根据msgId进行相关逻辑处理
}
@Override
public void onFailEvent(IMsgSendEvent event) {
System.out.println("发送失败(未发送到交换机),msgId" + event.getMsgId());
//TODO根据msgId进行相关逻辑处理,比如重发
}
@Override
public void onMissEvent(IMsgSendEvent event) {
System.out.println("消息丢失(交换机未能发送到队列),msgId" + event.getMsgId());
//TODO根据msgId进行相关逻辑处理
}
}
```
3.3监听主题上的消息(对于rabbitmq,主题对应路由键)
> spring中添加消息处理器bean,消息总线监听到相关主题的消息,即会调用相关的消息处理器,demo:
```
// topic :监听的主题
@RabbitMsgBusProcessor(topic = "topic2.#")
@Component
public class SimpleRabbitMsgBusProcessor implements IMsgProcessor{
//topic :具体的主题,如:topic2.demo , msg: 收到的消息内容
@Override
public void execute(String topic, String msg) {
Arrays.asList("SimpleRabbitMsgBusProcessor process msg:" + topic,msg).stream().forEach(System.out::println);
}
}
```
说明:
bean上必须添加@RabbitMsgBusProcessor注解并指定topic(支持#通配符)
**通用缓存组件开发文档**
如何使用?
一、项目中添加maven依赖
```
com.denghq
project-builder-component-basedatacache
```
二、yml文件中添加配置
```
com:
denghq:
component:
basedatacache:
failRetry: 3 #加载缓存失败重试次数,默认3
cacheTimeOutMin: 10 #缓存失效时间,单位:分,默认30分钟
maxThreadNum: 10 #加载缓存最大启动线程数,默认10
failRetryMaxSecond: 5 #加载缓存失败重试最大时间间隔,单位:秒,默认10秒
failRetryMinSecond: 2 #加载缓存失败重试最小时间间隔,单位:秒,默认3秒
useCache: #需加载的通用缓存数据
- AllDevice
- SomeDict
dictCodes: #字典缓存加载的字典项,如果不配置,不会加载
- 4
msgbus:
rabbitmq: #目前只支持了rabbitmq,已经预留了接口后面再扩展kafaka
enable: true #是否启用rabbitmq消息总线,不设置不会加载rabbitmq消息总线
queueList: # queue声明队列,支持声明多个队列
- name: denghq.${spring.application.name}.${spring.cloud.consul.discovery.ipAddress}:${server.port} #队列名称,规范: denghq.${服务标识}.${ip}:${port}
durable: true #是否持久化,含义同spring相关参数配置,默认:true
autoDelete: false #是否自动删除,含义同spring相关参数配置,默认:false
exchangeBinder: #声明交换机
# - name: denghq.Exchange.TianjinEPPV5 #交换机名称
- type: topic #交换机类型,含义同spring相关参数配置,默认:topic
durable: true #是否持久化,含义同spring相关参数配置,默认:true
autoDelete: true #是否自动删除,含义同spring相关参数配置,默认:false
bindings: #绑定路由键,用于消息监听,queueName:队列名称,routingKey:路由键
- queueName: denghq.${spring.application.name}.${spring.cloud.consul.discovery.ipAddress}:${server.port}
routingKey: ParsedData.Passing.#
- queueName: denghq.${spring.application.name}.${spring.cloud.consul.discovery.ipAddress}:${server.port}
routingKey: Database.Changed.FrontDevice
- queueName: denghq.${spring.application.name}.${spring.cloud.consul.discovery.ipAddress}:${server.port} routingKey: Database.Changed.Dictionary
# sender:
# defaultExchange: ${com.denghq.component.msgbus.rabbitmq.exchangeBinder[0].name} # 消息默认发送到的交换机
#topics: #配置某个主题的消息发送到的交换机,如果跟defaultExchange相同可省略,name:消息的主题,支持“#”通配符,exchange:交换机名称
# - {name: topic1.#,exchange: exchangeForMsgBus}
#- {name: topic2,exchange: exchangeForMsgBus}
msgReceiver:
maxMsgProcessThreadNum: 10 #调用消息处理启用的最大线程数,默认:10
maxConcurrentConsumers: 1 #消息处理最大消费者数量,默认:1
concurrentConsumers: 1 #消息处理最大消费者数量,默认:1
```
说明:
1. 因缓存组件需监听数据变更消息,故依赖消息总线组件,需配置消息总线,确保能监听到对应主题的消息,配置方法参考消息总线开发文档。
2. 目前已支持的缓存枚举信息如下:
/**
* 通用缓存信息枚举
*/
```
public enum CacheTypeEnum {
AllUser("Database.Changed.User", "所有用户信息缓存"),
AllDevice("Database.Changed.FrontDevice","所有设备信息"),
AllSpotting("Database.Changed.Spotting","所有路口信息"),
SomeDict("Database.Changed.Dictionary","字典数据信息"),
AllDeviceGroup("Database.Changed.FrontdeviceGroup","所有设备组信息" ),
AllSpottingGroup("Database.Changed.SpottingGroup","所有路口组信息" ),
AllVehicleRedlist("Database.Changed.VehicleRedlist","所有红名单信息" ),
;
private final String topic;
private final String cacheName;
CacheTypeEnum(String topic, String cacheName) {
this.topic = topic;
this.cacheName = cacheName;
}
public String getCacheName() {
return this.cacheName;
}
public String getTopic() {
return this.topic;
}
}
```
三、代码中使用
1、注入需要使用的缓存bean(bean信息参考java doc),如需获取红名单,demo:
```
@Autowired
private VehicleRedlistCache vehicleRedlistCache;
```
2、调用缓存bean的getLoadedCacheData方法,完整demo如下:
```
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {App.class})// 指定启动类
public class TestBC {
@Autowired
private VehicleRedlistCache vehicleRedlistCache;
@Test
public void testBaseDataCacheHolder() {
System.out.println(vehicleRedlistCache.getLoadedCacheData());
//baseDataCacheHolder.getAllCache().forEach(System.out::println);
}
}
```
执行结果:
四、如何扩展
对于缓存组件未支持的缓存信息,缓存组件支持扩展自定义缓存,方法如下:
1、编写缓存对象,实现ICustomCacheDomain接口,并继承AbstractCacheDomain抽象类,添加到spring容器。demo:
```
/**
* 设备缓存
*/
@Data
@Component
public class DeviceCache extends AbstractCacheDomain> implements ICustomCacheDomain> {
private String cacheName;//缓存名称
private String cacheKey;//缓存标识
private Boolean loadSuccess;//是否成功加载过数据
private String topicName;//监听的变更消息主题
private final List cacheData;//存放缓存数据
@Autowired
private CommonFrontDeviceService commonFrontDeviceService;
public DeviceCache(){
this.cacheName = CacheTypeEnum.AllDevice.getCacheName();
this.cacheKey = CacheTypeEnum.AllDevice.name();
this.topicName = CacheTypeEnum.AllDevice.getTopic();
this.loadSuccess = false;
cacheData = Lists.newArrayList();
}
@Override
public int initLoad() {
List all = commonFrontDeviceService.simpleAll();
this.cacheData.clear();
this.cacheData.addAll(all);
return all.size();
}
@Override
public int update() {
return initLoad();
}
}
```
2、使用
直接在需要使用的地方注入该bean,调用其getLoadedCacheData方法即可获取数据,缓存组件会自动加载缓存数据及监听变更消息刷新该缓存。
```
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {App.class})// 指定启动类
public class TestBC {
@Autowired
private DeviceCache deviceCache;
@Test
public void testBaseDataCacheHolder() {
System.out.println(deviceCache.getLoadedCacheData());
}
}
```
**配置管理组件开发文档**
一、如何使用?
1、添加maven依赖
```
com.denghq
project-builder-component-consul-config
```
2、在原springboot配置类中添加@Category、@ConfigAttribute注解
ex:
```
@Component
@ConfigurationProperties(prefix = "com.denghq.tdm")
@Category(projectName = "校时管理", categoryNo = "tdm/TDMProperties1", categoryName = "校时管理")
@Data
public class TDMProperties {
/**
* 时间源服务器地址配置
*/
@ConfigAttribute(name = "时间源服务器地址", valueType = ConfigValueTypeEnum.DATA_LIST)
private List ntpServerUrl;
/**
* 时间源服务器时间相差阈值(ms),超过阈值发送阻断消息
*/
@ConfigAttribute(name = "时间源相差阈值(ms)")
private Long remoteTimeDiffThreshold = 1000L;
/**
* 本地和校时服务器时间相差阈值(ms),超过阈值则需重置本地时间为校时服务器的时间
*/
@ConfigAttribute(name = "本地时间相差阈值(ms)")
private Long localTimeDiffThreshold = 1500L;
/**
* 校准的时间间隔 (ms)
*/
@ConfigAttribute(name = "校准的时间间隔(ms)")
private Long syncInterval = 2000L;
/**
* 连接ntp服务的超时时间设置
*/
@ConfigAttribute(name = "连接ntp服务超时时间(ms)")
private Integer ntpClientDefaultTimeout = 800;
/**
* 连接ntp服务的重试次数
*/
@ConfigAttribute(name = "连接ntp服务的重试次数")
private Integer ntpClientRetry = 1;
/**
* 当前是否已阻断
*/
@ConfigAttribute(name = "是否全局阻断")
private Boolean blocked;
}
```
3、直接注入配置类到spring 的bean中使用
ex:
```
/**
* 校时任务
*/
@Component
@Slf4j
public class SyncTimeTask {
@Autowired
private TDMProperties properties;
```
二、详细说明
1、功能说明
配置管理组件主要实现配置类的自动注册、在线编辑及实时同步的功能,实现微服务架构中参数配置的统一管理及简化代码开发。
2、注解说明
@Category注解用于描述配置的分类信息,相同分类的配置会显示在配置管理页面同一个栏目下。
|参数名称|含义|是否必填 |说明|
| --- | --- | --- | --- |
|configType| 配置类型| 否| 不填默认为业务类型配置,可选值见枚举。|
|autoRegiste| 是否注册到配置中心| 否| 不填默认为注册,只能注册appNo为当前微服务标识的配置,其他服务的配置只读。|
|projectName| 所属项目名称| 是| 管理页面显示的一级栏目名称,用于管理页面分栏目展示。|
|appNo| 所属微服务标识| 否| 默认为程序所在微服务的标识。|
|categoryNo| 分类唯一标识| 是| 全局唯一不可重复,以服务标识开头,ex: tdm/TDMProperties。|
|categoryName| 分类名称| 是| 配置的名称信息。|
|saveVerifyApi| 数据校验http请求地址| 否| 不填不进行数据校验。|
@ConfigAttribute注解用于描述配置的配置项信息,用于实现在配置管理页面展示和编辑配置内容。
|参数名称| 含义| 是否必填| 说明|
| --- | --- | --- | --- |
|key| 配置项唯一标识| 否| 不填默认为配置类对应属性名。|
|name| 配置项名称| 是| 配置管理页面显示的配置项label。|
|remark| 备注| 否| |
|valueType| 值类型| 否| 不填注册时通过上下文及java类型自动推测|
|dateFormat| 日期格式化| 否| 日期类型数据适用,可选值见枚举|
|dataSource| 非日期类型值来源| 否| 默认为直接录入,可选值见枚举|
|customDataList |自定义数据源 | 否| 配置的值从限定列表下拉选择时适用。|
|dataSourceValue| 数据源里面的Key字段| 否| 默认为 value 指定DataSourceApi时生效|
|dataSourceText| 数据源里面的Text字段| 否| 默认为 text 指定DataSourceApi时生效,多个逗号分隔会按照顺序拼接 如配置了A,B两个字段显示为A--B|
|dataSourceValueFormat| 下拉选择后显示的字段| 否| 下拉选择后显示的字段,比如下拉里面是abc-11111,但是选中后只想显示abc,此时可以用该字段|
|dataSourceApi| 下拉数据源的API接口地址| 否| 只支持get请求 dataSource为从接口获取结果时生效|
|parentKey| 父级key| 否| 用于配置项联动的显示隐藏控制|
|parentValue| 父级value| 否| 用于配置项联动的显示隐藏控制|
|customPage| 自定义配置页面地址| 否| 当ValueType为url时,对应的自定义配置页面地址,用于复杂个性化配置项|
3、枚举
配置类型
```
/**
* 配置类型
*/
public enum ConfigTypeEnum {
BUSINESS(1, "业务逻辑配置"),
PROGRAM(2, "程序本身的配置");
private final int value;
private final String description;
ConfigTypeEnum(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
}
```
配置值类型
```
/**
* 配置值类型
*/
public enum ConfigValueTypeEnum {
NUMBER(1, "数值类型"),
STRING(2, "字符串"),
BOOLEAN(3, "布尔型"),
DATE(4, "日期类型"),
DATE_RANGE(5, "日期区间"),
ARRAY(6, "数组类型"),
DATA_LIST(7, "数据列表"),
URL(8, "自定义页面"),
OBJECT(9,"对象类型"),
NULL(-1,"未指定,自动推测")
;
private final int value;
private final String description;
ConfigValueTypeEnum(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
}
```
非日期类型值来源
/**
* 非日期类型值来源 *默认1
*/
```
public enum DataSourceEnum {
INPUT(1, "直接录入"),
SELECT(2, "从限定列表下拉选择"),
API(3, "从接口获取结果中下拉选择"),
TEXT_EDITER(4,"大文本编辑器")
;
private final int value;
private final String description;
DataSourceEnum(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
}
```
日期格式化
```
package com.denghq.component.config.metadata.enums;
/**
* 日期格式化
*/
public enum DateFormatEnum {
DATETIME(1, "年月日时分秒"),
DATE(2, "年月日"),
YEAR_MONTH(3, "年月"),
YEAR(4, "年"),
TIME(5, "时分秒"),
HOUR_MIN(6, "时分"),
NULL(-1,"未指定" );
private final int value;
private final String description;
DateFormatEnum(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
}
```
三、其他说明
为了实现跟springcloud的整合,对于老的项目需要把application_prod.yml更名为bootstrap.yml。
Excel解析组件开发文档
一、如何使用?
1、添加maven依赖
```
com.denghq
project-builder-component-excelparser
```
2、程序中使用(推荐使用sax解析)
组件支持了dom和sax两种方式解析excel2003及excel2007文档,推荐使用sax解析,比较省内存。
Dom解析
> 使用dom解析excel 文档
```
private IExcelParser parser;
@Test
public void testDomXlsx() {
parser = new ExcelDomParser<>();
IParserParam parserParam = DefaultParserParam
.builder()
.excelInputStream(Thread.currentThread() //要解析的文件流
.getContextClassLoader()
.getResourceAsStream("test01.xlsx"))
.columnSize(4)//解析的列数
.sheetNum(IParserParam.FIRST_SHEET) // 解析的工作薄索引
.targetClass(User.class) // 需解析出的对象
.header(User.getHeader())// 校验标题,不传表示不校验标题
.firstRow(0) // 解析开始行
.build();
List> user = parser.parse(parserParam);
System.out.println(user);
}
```
> Sax解析
使用sax解析excel文档
使用方法同dom解析完全相同,除了创建的IExcelParser对象不同,dom解析对象为ExcelDomParser,sax解析对象为ExcelSaxParser。
```
@Test
public void testSheet02Xlsx() {
parser = new ExcelSaxParser<>();
IParserParam parserParam = DefaultParserParam.builder()
.excelInputStream(Thread.currentThread()
.getContextClassLoader().getResourceAsStream("test02.xlsx"))
.columnSize(4)
.sheetNum(IParserParam.FIRST_SHEET + 1)
.targetClass(User.class) //被解析对象
.header(User.getHeader())
.firstRow(0)
.build();
List> obj = parser.parse(parserParam);
obj.forEach(o -> {
System.out.println(o.getRowNo());
System.out.println(o.getData());
});
}
```
> 说明
组件提供了ExcelField注解来对excel的解析方式进行描述,@ExcelField的字段具体含义如下:
Index: 对应excel的列号(从0开始)。
Type: 对应excel单元格值的类型,用来做数据格式化,目前支持日期/普通字符串。
被解析对象的属性只能是String类型,获取数据后需自行处理,一般用来进行数据校验,返回对应字段的错误提示。
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 码云特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)