JeecgBoot与Elasticsearch深度整合:打造企业级智能检索系统
在数据驱动的时代,企业级应用对全文检索的需求日益增长。JeecgBoot作为领先的低代码开发平台,通过与Elasticsearch的无缝集成,为开发者提供了一套完整的企业级检索解决方案。本文将从技术架构、实现流程到性能优化,全面解析如何在JeecgBoot中构建高效、稳定的全文检索功能,帮助开发团队快速提升应用的搜索体验。
企业级检索方案的价值与优势
现代企业应用面临着数据量爆炸式增长的挑战,传统数据库的模糊查询已无法满足用户对检索效率和准确性的需求。JeecgBoot与Elasticsearch的整合方案应运而生,带来了多方面的显著优势:
核心价值亮点
- 开发效率提升:通过平台封装的模板工具类,开发者无需从零构建ES交互逻辑,平均可减少80%的重复代码工作
- 架构灵活性:支持单机、集群等多种部署模式,满足不同规模企业的需求
- 检索性能优化:基于Elasticsearch的分布式架构,实现毫秒级响应,支持每秒数十万次查询请求
- 功能完整性:提供索引管理、数据同步、复杂查询等全生命周期管理能力
技术整合优势
JeecgBoot的ES集成方案采用了分层设计思想,将复杂的检索逻辑封装为易用的API接口,同时保持了足够的扩展性。这种设计使开发者能够专注于业务逻辑实现,而非底层技术细节。
图:JeecgBoot与Elasticsearch的架构整合展示了数据流转与处理的完整流程
环境搭建与核心配置
要在JeecgBoot中启用Elasticsearch功能,需要完成一系列环境准备和配置工作。本节将详细介绍从环境检查到核心配置的全过程。
环境准备与依赖配置
系统环境要求:
- JDK 1.8或更高版本
- Maven 3.5+构建工具
- Elasticsearch 7.x或更高版本(推荐7.14+)
- 至少2GB可用内存(生产环境建议8GB以上)
依赖引入: JeecgBoot已内置ES相关依赖,只需在项目的pom.xml中确认以下依赖项:
<!-- Elasticsearch核心依赖 -->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base-core</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
核心配置详解
Elasticsearch的连接配置主要通过application.yml文件完成。以下是一个完整的配置示例:
jeecg:
elasticsearch:
# ES集群节点地址,多个节点用逗号分隔
cluster-nodes: 127.0.0.1:9200,192.168.1.100:9200
# 连接超时时间,单位毫秒
connect-timeout: 5000
# socket超时时间,单位毫秒
socket-timeout: 3000
# 是否启用连接检查
check-enabled: true
# 索引自动创建开关
auto-create-index: true
# 用户名(如启用ES安全认证)
username: elastic
# 密码(如启用ES安全认证)
password: changeme
配置类的源码位于jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/Elasticsearch.java,包含了所有可配置项的详细定义。
客户端初始化流程
JeecgBoot在JeecgBaseConfig类中完成了ES客户端的自动配置,核心代码如下:
@Configuration
public class JeecgBaseConfig {
@Bean
@ConditionalOnProperty(prefix = "jeecg.elasticsearch", name = "check-enabled", havingValue = "true")
public RestHighLevelClient elasticsearchClient(Elasticsearch elasticsearch) {
// 创建客户端构建器
RestClientBuilder builder = RestClient.builder(
elasticsearch.getClusterNodes().stream()
.map(node -> new HttpHost(node.split(":")[0], Integer.parseInt(node.split(":")[1]), "http"))
.toArray(HttpHost[]::new)
);
// 添加认证信息(如配置)
if (StringUtils.hasText(elasticsearch.getUsername()) && StringUtils.hasText(elasticsearch.getPassword())) {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(elasticsearch.getUsername(), elasticsearch.getPassword()));
builder.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
}
// 设置超时配置
builder.setRequestConfigCallback(requestConfigBuilder ->
requestConfigBuilder
.setConnectTimeout(elasticsearch.getConnectTimeout())
.setSocketTimeout(elasticsearch.getSocketTimeout()));
return new RestHighLevelClient(builder);
}
}
功能实现与代码示例
JeecgBoot提供了JeecgElasticsearchTemplate模板类,封装了常用的ES操作。通过这个工具类,开发者可以轻松实现索引管理、数据CRUD和高级查询等功能。
索引管理基础操作
索引是Elasticsearch存储数据的基本单元,合理的索引设计直接影响检索性能。以下是使用模板类进行索引管理的示例:
@Service
public class UserIndexService {
@Autowired
private JeecgElasticsearchTemplate esTemplate;
// 创建用户索引
public boolean createUserIndex() {
// 索引名称
String indexName = "user_index";
// 检查索引是否已存在
if (esTemplate.indexExists(indexName)) {
log.info("索引[{}]已存在", indexName);
return false;
}
// 定义索引映射
Map<String, Object> mappings = new HashMap<>();
Map<String, Object> properties = new HashMap<>();
// 用户名:keyword类型,不分词,用于精确匹配
Map<String, Object> username = new HashMap<>();
username.put("type", "keyword");
properties.put("username", username);
// 昵称:text类型,支持分词检索
Map<String, Object> nickname = new HashMap<>();
nickname.put("type", "text");
nickname.put("analyzer", "ik_max_word"); // 使用IK中文分词器
nickname.put("search_analyzer", "ik_smart");
properties.put("nickname", nickname);
// 年龄:integer类型
Map<String, Object> age = new HashMap<>();
age.put("type", "integer");
properties.put("age", age);
// 注册时间:date类型
Map<String, Object> registerTime = new HashMap<>();
registerTime.put("type", "date");
registerTime.put("format", "yyyy-MM-dd HH:mm:ss");
properties.put("registerTime", registerTime);
mappings.put("properties", properties);
// 创建索引
return esTemplate.createIndex(indexName, mappings);
}
// 删除索引
public boolean deleteUserIndex() {
String indexName = "user_index";
if (esTemplate.indexExists(indexName)) {
return esTemplate.deleteIndex(indexName);
}
return true;
}
}
数据操作完整示例
数据操作是ES集成的核心功能,JeecgElasticsearchTemplate提供了丰富的API来实现数据的增删改查:
@Service
public class UserSearchService {
@Autowired
private JeecgElasticsearchTemplate esTemplate;
private static final String INDEX_NAME = "user_index";
private static final String TYPE_NAME = "_doc";
// 添加或更新用户数据
public boolean saveOrUpdateUser(UserDTO user) {
// 将DTO转换为Map
Map<String, Object> data = new HashMap<>();
data.put("id", user.getId());
data.put("username", user.getUsername());
data.put("nickname", user.getNickname());
data.put("age", user.getAge());
data.put("registerTime", user.getRegisterTime());
data.put("address", user.getAddress());
return esTemplate.saveOrUpdate(INDEX_NAME, TYPE_NAME, user.getId(), data);
}
// 批量添加用户数据
public void batchSaveUsers(List<UserDTO> userList) {
List<Map<String, Object>> dataList = userList.stream().map(user -> {
Map<String, Object> data = new HashMap<>();
data.put("id", user.getId());
data.put("username", user.getUsername());
data.put("nickname", user.getNickname());
data.put("age", user.getAge());
data.put("registerTime", user.getRegisterTime());
data.put("address", user.getAddress());
return data;
}).collect(Collectors.toList());
esTemplate.saveBatch(INDEX_NAME, TYPE_NAME, dataList);
}
// 根据ID查询用户
public Map<String, Object> getUserById(String id) {
return esTemplate.get(INDEX_NAME, TYPE_NAME, id);
}
// 删除用户
public boolean deleteUser(String id) {
return esTemplate.delete(INDEX_NAME, TYPE_NAME, id);
}
}
高级检索功能实现
JeecgBoot支持构建复杂的查询条件,满足企业级应用的多样化检索需求:
@Service
public class UserQueryService {
@Autowired
private JeecgElasticsearchTemplate esTemplate;
private static final String INDEX_NAME = "user_index";
private static final String TYPE_NAME = "_doc";
// 高级搜索示例:多条件组合查询
public PageInfo<Map<String, Object>> searchUsers(UserSearchVO searchVO, PageVO pageVO) {
// 创建查询构建器
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键词搜索(昵称或地址中包含关键词)
if (StringUtils.hasText(searchVO.getKeyword())) {
boolQuery.should(QueryBuilders.matchQuery("nickname", searchVO.getKeyword()).boost(3.0f));
boolQuery.should(QueryBuilders.matchQuery("address", searchVO.getKeyword()));
}
// 年龄范围查询
if (searchVO.getMinAge() != null && searchVO.getMaxAge() != null) {
boolQuery.filter(QueryBuilders.rangeQuery("age")
.gte(searchVO.getMinAge())
.lte(searchVO.getMaxAge()));
} else if (searchVO.getMinAge() != null) {
boolQuery.filter(QueryBuilders.rangeQuery("age").gte(searchVO.getMinAge()));
} else if (searchVO.getMaxAge() != null) {
boolQuery.filter(QueryBuilders.rangeQuery("age").lte(searchVO.getMaxAge()));
}
// 注册时间范围查询
if (searchVO.getStartTime() != null && searchVO.getEndTime() != null) {
boolQuery.filter(QueryBuilders.rangeQuery("registerTime")
.format("yyyy-MM-dd HH:mm:ss")
.gte(searchVO.getStartTime())
.lte(searchVO.getEndTime()));
}
// 构建排序条件
List<SortOrder> sortOrders = new ArrayList<>();
if (StringUtils.hasText(searchVO.getSortField())) {
SortOrder.Direction direction = "desc".equalsIgnoreCase(searchVO.getSortDirection())
? SortOrder.Direction.DESC : SortOrder.Direction.ASC;
sortOrders.add(new SortOrder(searchVO.getSortField(), direction));
} else {
// 默认按注册时间降序排序
sortOrders.add(new SortOrder("registerTime", SortOrder.Direction.DESC));
}
// 执行查询
return esTemplate.search(INDEX_NAME, TYPE_NAME, boolQuery, pageVO.getPageNo(),
pageVO.getPageSize(), sortOrders);
}
}
场景化实现案例:用户检索系统
以下是一个完整的用户检索系统实现案例,展示了从数据同步到前端展示的全流程:
1. 数据同步服务:定时将数据库用户数据同步到ES索引
@Service
@Slf4j
public class UserDataSyncService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserSearchService userSearchService;
// 定时同步用户数据
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
public void syncUserData() {
log.info("开始同步用户数据到Elasticsearch...");
// 查询需要同步的用户数据(这里简化处理,实际应根据更新时间增量同步)
List<User> userList = userMapper.selectList(null);
if (CollectionUtils.isEmpty(userList)) {
log.info("没有需要同步的用户数据");
return;
}
// 转换为DTO
List<UserDTO> userDTOList = userList.stream().map(user -> {
UserDTO dto = new UserDTO();
BeanUtils.copyProperties(user, dto);
return dto;
}).collect(Collectors.toList());
// 批量保存到ES
userSearchService.batchSaveUsers(userDTOList);
log.info("用户数据同步完成,共同步 {} 条记录", userDTOList.size());
}
}
2. 控制器层实现:提供REST接口供前端调用
@RestController
@RequestMapping("/api/es/user")
public class UserEsController {
@Autowired
private UserQueryService userQueryService;
@Autowired
private UserSearchService userSearchService;
/**
* 用户高级搜索
*/
@PostMapping("/search")
public Result<PageInfo<Map<String, Object>>> searchUsers(
@RequestBody @Valid UserSearchVO searchVO,
PageVO pageVO) {
PageInfo<Map<String, Object>> pageInfo = userQueryService.searchUsers(searchVO, pageVO);
return Result.OK(pageInfo);
}
/**
* 根据ID获取用户详情
*/
@GetMapping("/{id}")
public Result<Map<String, Object>> getUserById(@PathVariable String id) {
Map<String, Object> user = userSearchService.getUserById(id);
if (user == null) {
return Result.error("用户不存在");
}
return Result.OK(user);
}
}
3. 前端界面实现:Vue3组件展示搜索结果
<template>
<div class="search-container">
<div class="search-form">
<a-form :model="searchForm" layout="inline" @submit.prevent="handleSearch">
<a-form-item label="关键词">
<a-input v-model:value="searchForm.keyword" placeholder="请输入昵称或地址关键词" />
</a-form-item>
<a-form-item label="年龄范围">
<a-input-number v-model:value="searchForm.minAge" placeholder="最小年龄" style="width: 100px" />
<span style="margin: 0 10px">至</span>
<a-input-number v-model:value="searchForm.maxAge" placeholder="最大年龄" style="width: 100px" />
</a-form-item>
<a-form-item label="注册时间">
<a-range-picker
v-model:value="searchForm.timeRange"
format="YYYY-MM-DD HH:mm:ss"
placeholder="选择注册时间范围"
/>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit">搜索</a-button>
<a-button style="margin-left: 10px" @click="resetForm">重置</a-button>
</a-form-item>
</a-form>
</div>
<div class="search-result">
<a-table
:columns="columns"
:data-source="userList"
:pagination="pagination"
@change="handleTableChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { userSearch } from '@/api/sys/user';
const searchForm = reactive({
keyword: '',
minAge: null,
maxAge: null,
timeRange: null
});
const columns = [
{ title: '用户名', dataIndex: 'username', key: 'username' },
{ title: '昵称', dataIndex: 'nickname', key: 'nickname' },
{ title: '年龄', dataIndex: 'age', key: 'age' },
{ title: '注册时间', dataIndex: 'registerTime', key: 'registerTime' },
{ title: '地址', dataIndex: 'address', key: 'address' },
{ title: '操作', key: 'action', render: (_, record) => (
<a-button type="text" @click="viewUser(record.id)">查看详情</a-button>
)}
];
const userList = ref([]);
const pagination = ref({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true
});
const handleSearch = async () => {
pagination.value.current = 1;
await fetchUserList();
};
const fetchUserList = async () => {
const params = {
...searchForm,
startTime: searchForm.timeRange ? searchForm.timeRange[0] : null,
endTime: searchForm.timeRange ? searchForm.timeRange[1] : null,
pageNo: pagination.value.current,
pageSize: pagination.value.pageSize
};
const res = await userSearch(params);
if (res.success) {
userList.value = res.result.list;
pagination.value.total = res.result.total;
}
};
const handleTableChange = (pagination) => {
pagination.value = pagination;
fetchUserList();
};
const resetForm = () => {
Object.assign(searchForm, {
keyword: '',
minAge: null,
maxAge: null,
timeRange: null
});
};
const viewUser = (id) => {
// 查看用户详情逻辑
console.log('查看用户详情:', id);
};
// 初始加载
fetchUserList();
</script>
图:用户检索系统界面展示了高级搜索表单和结果展示区域
最佳实践与性能优化
要充分发挥JeecgBoot与Elasticsearch整合的优势,需要遵循一些最佳实践并进行针对性的性能优化。本节将介绍几个关键的优化方向和实施方法。
索引设计优化
合理的索引设计是提升检索性能的基础,以下是几个关键的优化建议:
1. 合理设置分片和副本
根据数据量和查询并发量调整分片数量,一般建议:
- 每个分片大小控制在20-40GB之间
- 分片数量 = 数据总量 / 30GB
- 副本数量根据高可用性要求设置,生产环境建议1-2个副本
2. 字段类型优化
- 对不需要分词的字段使用keyword类型(如ID、手机号)
- 对需要全文检索的字段使用text类型,并配置合适的分词器
- 对数值类型使用具体的数值类型(integer、long、float等)而非text
- 对日期类型使用date类型,并统一格式
3. 映射优化示例
// 优化的索引映射配置
Map<String, Object> mappings = new HashMap<>();
Map<String, Object> properties = new HashMap<>();
// 用户名:keyword类型,适合精确匹配和聚合分析
Map<String, Object> username = new HashMap<>();
username.put("type", "keyword");
properties.put("username", username);
// 昵称:text类型,使用IK分词器,并设置keyword子字段用于排序和聚合
Map<String, Object> nickname = new HashMap<>();
nickname.put("type", "text");
nickname.put("analyzer", "ik_max_word");
Map<String, Object> nicknameKeyword = new HashMap<>();
nicknameKeyword.put("type", "keyword");
nicknameKeyword.put("ignore_above", 256);
nickname.put("fields", Map.of("keyword", nicknameKeyword));
properties.put("nickname", nickname);
// 年龄:integer类型,适合范围查询
Map<String, Object> age = new HashMap<>();
age.put("type", "integer");
properties.put("age", age);
// 注册时间:date类型,设置格式化
Map<String, Object> registerTime = new HashMap<>();
registerTime.put("type", "date");
registerTime.put("format", "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis");
properties.put("registerTime", registerTime);
mappings.put("properties", properties);
查询性能优化
查询性能直接影响用户体验,以下是几个实用的优化技巧:
1. 使用过滤器缓存
将频繁使用的过滤条件(如状态、类型等)放在filter子句中,Elasticsearch会自动缓存这些条件的结果:
// 优化前
boolQuery.must(QueryBuilders.termQuery("status", 1));
// 优化后 - 使用filter子句,结果会被缓存
boolQuery.filter(QueryBuilders.termQuery("status", 1));
2. 控制返回字段
只返回需要的字段,减少网络传输和内存消耗:
// 只返回指定字段
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.fetchSource(new String[]{"id", "username", "nickname", "age"}, null);
3. 避免深度分页
使用scroll API或search after代替from+size进行深度分页:
// 使用search after进行分页
SearchResponse response = client.search(new SearchRequest(indexName)
.source(new SearchSourceBuilder()
.query(query)
.size(10)
.searchAfter(lastSortValues)), RequestOptions.DEFAULT);
数据同步策略
保持ES数据与数据库数据一致性是生产环境必须解决的问题,推荐以下同步策略:
1. 增量同步
通过时间戳或版本号跟踪数据变更,只同步更新的数据:
// 增量同步示例
public void incrementalSync() {
// 获取上次同步时间
Date lastSyncTime = syncRecordService.getLastSyncTime("user");
// 查询上次同步时间之后更新的数据
List<User> updateUsers = userMapper.selectByUpdateTimeAfter(lastSyncTime);
if (CollectionUtils.isNotEmpty(updateUsers)) {
// 同步更新的数据到ES
userSearchService.batchSaveUsers(convertToDTO(updateUsers));
// 更新同步时间
syncRecordService.updateSyncTime("user", new Date());
}
}
2. 事务日志同步
通过数据库事务日志(如MySQL的binlog)捕获数据变更,实现准实时同步。JeecgBoot可以集成Canal等工具实现这一功能。
3. 定时全量同步
作为数据一致性的最后保障,定期执行全量同步,修复可能的数据不一致问题。
常见问题与解决方案
在JeecgBoot集成Elasticsearch的过程中,开发者可能会遇到各种问题。本节总结了几个常见问题及其解决方案。
连接问题排查
问题现象:应用启动时无法连接到Elasticsearch集群
排查步骤:
- 检查网络连通性:使用
telnet es-host 9200测试网络连接 - 检查ES服务状态:通过
curl http://es-host:9200确认ES是否正常运行 - 检查配置参数:确认
cluster-nodes配置是否正确,端口是否被防火墙阻止 - 检查认证信息:如果ES启用了安全认证,确认用户名密码是否正确
解决方案:
# 增加连接调试日志
logging:
level:
org.elasticsearch.client: debug
索引创建失败
问题现象:调用createIndex方法返回false,索引创建失败
可能原因:
- 索引已存在:先调用indexExists检查索引状态
- 权限不足:检查ES用户是否有创建索引的权限
- 映射格式错误:检查映射定义是否符合ES要求
- 磁盘空间不足:ES在磁盘空间低于阈值时会阻止创建索引
解决方案:
// 安全的索引创建方法
public boolean safeCreateIndex(String indexName, Map<String, Object> mappings) {
if (esTemplate.indexExists(indexName)) {
log.warn("索引[{}]已存在,跳过创建", indexName);
return true;
}
try {
boolean result = esTemplate.createIndex(indexName, mappings);
if (!result) {
log.error("索引[{}]创建失败,尝试获取ES错误信息", indexName);
// 获取ES集群状态,检查可能的错误原因
Map<String, Object> clusterHealth = esTemplate.getClusterHealth();
log.error("ES集群状态: {}", clusterHealth);
}
return result;
} catch (Exception e) {
log.error("创建索引[{}]时发生异常", indexName, e);
return false;
}
}
数据同步异常
问题现象:数据同步到ES后查询结果与数据库不一致
排查方案:
- 检查同步逻辑:确认数据转换和映射是否正确
- 检查同步日志:查看同步过程中是否有错误日志
- 手动对比数据:随机抽取部分数据对比数据库和ES中的内容
- 检查ES索引刷新:数据写入后可能需要等待索引刷新(默认1秒)
解决方案:
// 强制刷新索引,确保数据立即可见(生产环境谨慎使用)
esTemplate.refreshIndex(indexName);
学习资源与进阶路径
JeecgBoot的Elasticsearch集成功能为开发者提供了快速构建企业级检索系统的能力。要进一步提升这方面的技术能力,可以参考以下学习资源和进阶路径。
官方文档与源码学习
- JeecgBoot官方文档:提供了ES集成的基础配置和使用说明
- 核心模板类源码:
JeecgElasticsearchTemplate的实现位于jeecg-boot-base-core/src/main/java/org/jeecg/common/es/JeecgElasticsearchTemplate.java - 配置类源码:
Elasticsearch配置类位于jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/Elasticsearch.java
进阶学习路径
1. Elasticsearch核心原理
- 深入学习ES的分布式架构和分片机制
- 理解倒排索引和相关性评分原理
- 掌握ES的查询DSL语法和执行机制
2. 高级功能应用
- 学习聚合分析(Aggregation)在业务统计中的应用
- 掌握ES的地理位置搜索功能
- 了解ES的机器学习功能,实现异常检测
3. 性能调优实践
- 学习ES集群的规划与部署最佳实践
- 掌握慢查询分析和优化方法
- 了解ES的监控和告警机制
社区与生态
- JeecgBoot社区:可以在社区论坛中提问和分享经验
- Elastic官方社区:获取最新的ES技术资讯和最佳实践
- GitHub项目:参与JeecgBoot项目贡献,或查看其他优秀的集成案例
通过不断学习和实践,开发者可以充分发挥JeecgBoot与Elasticsearch整合的优势,构建出高性能、高可用的企业级检索系统,为业务提供强大的数据支持。
GLM-5智谱 AI 正式发布 GLM-5,旨在应对复杂系统工程和长时域智能体任务。Jinja00
GLM-5.1GLM-5.1是智谱迄今最智能的旗舰模型,也是目前全球最强的开源模型。GLM-5.1大大提高了代码能力,在完成长程任务方面提升尤为显著。和此前分钟级交互的模型不同,它能够在一次任务中独立、持续工作超过8小时,期间自主规划、执行、自我进化,最终交付完整的工程级成果。Jinja00
LongCat-AudioDiT-1BLongCat-AudioDiT 是一款基于扩散模型的文本转语音(TTS)模型,代表了当前该领域的最高水平(SOTA),它直接在波形潜空间中进行操作。00- QQwen3.5-397B-A17BQwen3.5 实现了重大飞跃,整合了多模态学习、架构效率、强化学习规模以及全球可访问性等方面的突破性进展,旨在为开发者和企业赋予前所未有的能力与效率。Jinja00
HY-Embodied-0.5这是一套专为现实世界具身智能打造的基础模型。该系列模型采用创新的混合Transformer(Mixture-of-Transformers, MoT) 架构,通过潜在令牌实现模态特异性计算,显著提升了细粒度感知能力。Jinja00
FreeSql功能强大的对象关系映射(O/RM)组件,支持 .NET Core 2.1+、.NET Framework 4.0+、Xamarin 以及 AOT。C#00

