diff --git a/pom.xml b/pom.xml index 9535b97e1e..eb370140ad 100644 --- a/pom.xml +++ b/pom.xml @@ -15,14 +15,14 @@ yudao-module-system yudao-module-infra - - - - - - - - + yudao-module-member + yudao-module-bpm + yudao-module-report + yudao-module-mp + yudao-module-pay + yudao-module-mall + yudao-module-crm + yudao-module-erp diff --git a/script/笔记/Elasticsearch/Elasticsearch.postman_collection.json b/script/笔记/Elasticsearch/Elasticsearch.postman_collection.json new file mode 100644 index 0000000000..c65d8733d2 --- /dev/null +++ b/script/笔记/Elasticsearch/Elasticsearch.postman_collection.json @@ -0,0 +1,32 @@ +{ + "info": { + "_postman_id": "44c9067c-39e0-4ba4-b164-67f65d9988a5", + "name": "Elasticsearch", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "40143787" + }, + "item": [ + { + "name": "http://192.168.172.128:9200/", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://192.168.172.128:9200/", + "protocol": "http", + "host": [ + "192", + "168", + "172", + "128" + ], + "port": "9200", + "path": [ + "" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/script/笔记/Elasticsearch/elasticsearch.yml b/script/笔记/Elasticsearch/elasticsearch.yml new file mode 100644 index 0000000000..517f1dfec5 --- /dev/null +++ b/script/笔记/Elasticsearch/elasticsearch.yml @@ -0,0 +1,119 @@ +# ======================== Elasticsearch Configuration ========================= +# +# NOTE: Elasticsearch comes with reasonable defaults for most settings. +# Before you set out to tweak and tune the configuration, make sure you +# understand what are you trying to accomplish and the consequences. +# +# The primary way of configuring a node is via this file. This template lists +# the most important settings you may want to configure for a production cluster. +# +# Please consult the documentation for further information on configuration options: +# https://www.elastic.co/guide/en/elasticsearch/reference/index.html +# +# ---------------------------------- Cluster ----------------------------------- +# +# Use a descriptive name for your cluster: +# +cluster.name: my-application +# +# ------------------------------------ Node ------------------------------------ +# +# Use a descriptive name for the node: +# +node.name: node-1 +# +# Add custom attributes to the node: +# +#node.attr.rack: r1 +# +# ----------------------------------- Paths ------------------------------------ +# +# Path to directory where to store the data (separate multiple locations by comma): +# +path.data: /var/lib/elasticsearch +# +# Path to log files: +# +path.logs: /var/log/elasticsearch +# +# ----------------------------------- Memory ----------------------------------- +# +# Lock the memory on startup: +# +#bootstrap.memory_lock: true +# +# Make sure that the heap size is set to about half the memory available +# on the system and that the owner of the process is allowed to use this +# limit. +# +# Elasticsearch performs poorly when the system is swapping the memory. +# +# ---------------------------------- Network ----------------------------------- +# +# By default Elasticsearch is only accessible on localhost. Set a different +# address here to expose this node on the network: +# +network.host: 0.0.0.0 +# +# By default Elasticsearch listens for HTTP traffic on the first free port it +# finds starting at 9200. Set a specific HTTP port here: +# +http.port: 9200 +# +# For more information, consult the network module documentation. +# +# --------------------------------- Discovery ---------------------------------- +# +# Pass an initial list of hosts to perform discovery when this node is started: +# The default list of hosts is ["127.0.0.1", "[::1]"] +# +discovery.seed_hosts: ["127.0.0.1"] +# +# Bootstrap the cluster using an initial set of master-eligible nodes: +# +cluster.initial_master_nodes: ["node-1"] +# +# For more information, consult the discovery and cluster formation module documentation. +# +# ---------------------------------- Various ----------------------------------- +# +# Allow wildcard deletion of indices: +# +#action.destructive_requires_name: false + +#----------------------- BEGIN SECURITY AUTO CONFIGURATION ----------------------- +# +# The following settings, TLS certificates, and keys have been automatically +# generated to configure Elasticsearch security features on 02-12-2024 11:13:43 +# +# -------------------------------------------------------------------------------- + +# Enable security features +xpack.security.enabled: false + +xpack.security.enrollment.enabled: false + +# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents +xpack.security.http.ssl: + enabled: false + keystore.path: certs/http.p12 + +# Enable encryption and mutual authentication between cluster nodes +xpack.security.transport.ssl: + enabled: false + verification_mode: certificate + keystore.path: certs/transport.p12 + truststore.path: certs/transport.p12 +# Create a new cluster with the current node only +# Additional nodes can still join the cluster later +#cluster.initial_master_nodes: ["localhost"] + +# Allow HTTP API connections from anywhere +# Connections are encrypted and require user authentication +#http.host: 0.0.0.0 + +# Allow other nodes to join the cluster from anywhere +# Connections are encrypted and mutually authenticated +#transport.host: 0.0.0.0 + +#----------------------- END SECURITY AUTO CONFIGURATION ------------------------- diff --git a/script/笔记/Elasticsearch/安装.txt b/script/笔记/Elasticsearch/安装.txt new file mode 100644 index 0000000000..fcc7fc007f --- /dev/null +++ b/script/笔记/Elasticsearch/安装.txt @@ -0,0 +1,4 @@ +rpm -ivh elasticsearch-8.16.1-x86_64.rpm + +systemctl start elasticsearch.service +systemctl enable elasticsearch.service diff --git a/script/笔记/MySQL/all_tables_backup.zip b/script/笔记/MySQL/all_tables_backup.zip new file mode 100644 index 0000000000..c07d1b492b Binary files /dev/null and b/script/笔记/MySQL/all_tables_backup.zip differ diff --git a/script/笔记/MySQL/笔记.txt b/script/笔记/MySQL/笔记.txt new file mode 100644 index 0000000000..62e34857e5 --- /dev/null +++ b/script/笔记/MySQL/笔记.txt @@ -0,0 +1,36 @@ +mysqldump -u root -p123456 ruoyi-vue-pro > all_tables_backup.sql + +systemctl stop firewalld.service +systemctl disable firewalld.service + +一、安装mysql +yum install mysql-server +设置表名带小写敏感忽略 +vim /etc/my.cnf.d/mysql-server.cnf 末尾添加lower_case_table_names=1 + +systemctl start mysqld #启动MYSQL 服务 +systemctl enable mysqld #将MYSQL 服务设置为开机启动 + +mysql -uroot #新安装MYSQL 可以使用ROOT 无密码直接登录 +use mysql; #选择系统数据库 +alter user 'root'@'localhost' identified by '123456'; #将123456 替换成自己的密码 +update user set host='%' where user='root'; +flush privileges; #刷新权限表 + + +CREATE database if NOT EXISTS `ruoyi-vue-pro` default character set utf8mb4 collate utf8mb4_general_ci; +USE `ruoyi-vue-pro`; + + + +安装Redis +#添加EPEL仓库 +sudo yum install epel-release +#更新yum源 +sudo yum update +安装 +yum install redis +systemctl start redis +systemctl enable redis +vim /etc/redis/redis.conf +systemctl restart redis \ No newline at end of file diff --git a/script/笔记/记录.txt b/script/笔记/记录.txt new file mode 100644 index 0000000000..4b7b031212 --- /dev/null +++ b/script/笔记/记录.txt @@ -0,0 +1,4 @@ + @InterceptorIgnore(tenantLine = "true") + + ElasticSearch 命令行乱码: + 到 \config 文件下找到 jvm.options 文件 打开后 在文件末尾空白处 添加 -Dfile.encoding=GBK 保存后重启即可。 \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-elasticsearch/pom.xml b/yudao-framework/yudao-spring-boot-starter-elasticsearch/pom.xml new file mode 100644 index 0000000000..079851147d --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-elasticsearch/pom.xml @@ -0,0 +1,36 @@ + + + + cn.iocoder.boot + yudao-framework + ${revision} + + 4.0.0 + yudao-spring-boot-starter-elasticsearch + jar + + ${project.artifactId} + Elasticsearch 封装拓展 + https://github.com/YunaiV/ruoyi-vue-pro + + + + cn.iocoder.boot + yudao-common + + + + cn.iocoder.boot + yudao-spring-boot-starter-web + + + + co.elastic.clients + elasticsearch-java + 8.16.1 + + + + diff --git a/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/ClientUtil.java b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/ClientUtil.java new file mode 100644 index 0000000000..454aaac186 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/ClientUtil.java @@ -0,0 +1,100 @@ +package cn.iocoder.yudao.framework.elasticsearch; + +import cn.iocoder.yudao.framework.elasticsearch.dataobject.ProductInfo; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery; +import co.elastic.clients.elasticsearch.core.*; +import co.elastic.clients.elasticsearch.indices.Alias; +import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; +import co.elastic.clients.elasticsearch.indices.CreateIndexResponse; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; + +import java.io.IOException; + +public class ClientUtil { + + /** + * 初始化客户端 + * + * @return + */ + private static ElasticsearchClient initClient() { + String hostname = "192.168.*.*"; + int port = 9200; + String username = "your username"; + String password = "your password"; + // 基本的用户名密码认证 + BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider(); + basicCredentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); + RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost(hostname, port, "http")); + restClientBuilder.setHttpClientConfigCallback(httpAsyncClientBuilder -> + httpAsyncClientBuilder.setDefaultCredentialsProvider(basicCredentialsProvider)); + RestClient restClient = restClientBuilder.build(); + ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); + return new ElasticsearchClient(transport); + } + + /** + * 创建索引 + */ + private static void createIndex() throws IOException { + ElasticsearchClient esClient = initClient(); + CreateIndexResponse createIndexResponse = esClient.indices().create( + new CreateIndexRequest.Builder() + .index("product_info") + .aliases("product_info_alias", + new Alias.Builder().isWriteIndex(true).build() + ).build()); + if (createIndexResponse.acknowledged()) { + System.out.println("创建索引成功!"); + } else { + System.out.println("创建索引失败!"); + } + } + + private static void addData() throws IOException { + ElasticsearchClient esClient = initClient(); + ProductInfo productInfo = new ProductInfo("红米手机", "红米 Note,智能手机,价格便宜", "蓝色", 999); + CreateResponse createResponse = esClient.create(createRequest -> createRequest.id("1003").index("product_info").document(productInfo)); + System.out.println("添加索引请求结果:" + createResponse.result()); + } + + private static void delDataVidId(String id) throws IOException { + ElasticsearchClient esClient = initClient(); + DeleteResponse deleteResponse = esClient.delete(deleteReqest -> deleteReqest.index("product_info").id(id)); + System.out.println("按照 ID 删除索引数据结果:" + deleteResponse.result()); + } + + private static void delDataVidQuery(String value) throws IOException { + ElasticsearchClient esClient = initClient(); + DeleteByQueryResponse deleteByQueryResponse = esClient.deleteByQuery(deleteByQuery -> + deleteByQuery.index("product_info").query(query -> + query.match(MatchQuery.of(builder -> + builder.field("productName").query(value))))); + System.out.println("按照条件删除索引数据结果:" + deleteByQueryResponse.deleted()); + } + + private static void updateViaId(String id) throws IOException { + ElasticsearchClient esClient = initClient(); + ProductInfo productInfo = new ProductInfo("苹果手机", "苹果智能手机,价格实惠便宜", "黑色", 5999); + UpdateResponse updateResponse = esClient.update(updateRequest -> updateRequest.index("product_info").id(id).doc(productInfo), ProductInfo.class); + System.out.println("根据 ID 修改:" + updateResponse.result()); + } + + private static void query(String value) throws IOException { + ElasticsearchClient esClient = initClient(); + SearchResponse searchResponse = esClient.search(searchRequest -> + searchRequest.index("product_info").query(q -> q.match(MatchQuery.of(builder -> + builder.field("productName").query(value)))), ProductInfo.class); + searchResponse.hits().hits().forEach(productInfoHit -> System.out.println(productInfoHit)); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/core/JeecgElasticsearchTemplate.java b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/core/JeecgElasticsearchTemplate.java new file mode 100644 index 0000000000..15d3a73a10 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/core/JeecgElasticsearchTemplate.java @@ -0,0 +1,554 @@ +package cn.iocoder.yudao.framework.elasticsearch.core; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +import java.util.*; + +/** + * 关于 ElasticSearch 的一些方法(创建索引、添加数据、查询等) + * + * @author sunjianlei + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "elasticsearch", name = "cluster-nodes") +public class JeecgElasticsearchTemplate { + /** + * es服务地址 + */ + private String baseUrl; + private final String FORMAT_JSON = "format=json"; + /** + * Elasticsearch 的版本号 + */ + private String version = null; + + /** + * ElasticSearch 最大可返回条目数 + */ + public static final int ES_MAX_SIZE = 10000; + + /** + * es7 + */ + public static final String IE_SEVEN = "7"; + + /** + * url not found 404 + */ + public static final String URL_NOT_FOUND = "404 Not Found"; + + public JeecgElasticsearchTemplate(@Value("${elasticsearch.cluster-nodes}") String baseUrl, @Value("${elasticsearch.check-enabled}") boolean checkEnabled) { + log.debug("JeecgElasticsearchTemplate BaseURL:" + baseUrl); + if (StringUtils.isNotEmpty(baseUrl)) { + this.baseUrl = baseUrl; + // 验证配置的ES地址是否有效 + if (checkEnabled) { + try { + this.getElasticsearchVersion(); + log.info("ElasticSearch 服务连接成功"); + log.info("ElasticSearch version: " + this.version); + } catch (Exception e) { + this.version = ""; + log.warn("ElasticSearch 服务连接失败,原因:配置未通过。可能是BaseURL未配置或配置有误,也可能是Elasticsearch服务未启动。接下来将会拒绝执行任何方法!"); + } + } + } + } + + /** + * 获取 Elasticsearch 的版本号信息,失败返回null + */ + private void getElasticsearchVersion() { + if (this.version == null) { + String url = this.getBaseUrl().toString(); + JSONObject result = RestUtil.get(url); + if (result != null) { + JSONObject v = result.getJSONObject("version"); + this.version = v.getString("number"); + } + } + } + + public StringBuilder getBaseUrl(String indexName, String typeName) { + typeName = typeName.trim().toLowerCase(); + return this.getBaseUrl(indexName).append("/").append(typeName); + } + + public StringBuilder getBaseUrl(String indexName) { + indexName = indexName.trim().toLowerCase(); + return this.getBaseUrl().append("/").append(indexName); + } + + public StringBuilder getBaseUrl() { + return new StringBuilder("http://").append(this.baseUrl); + } + + /** + * cat 查询ElasticSearch系统数据,返回json + */ + private ResponseEntity cat(String urlAfter, Class responseType) { + String url = this.getBaseUrl().append("/_cat").append(urlAfter).append("?").append(FORMAT_JSON).toString(); + return RestUtil.request(url, HttpMethod.GET, null, null, null, responseType); + } + + /** + * 查询所有索引 + *

+ * 查询地址:GET http://{baseUrl}/_cat/indices + */ + public JSONArray getIndices() { + return getIndices(null); + } + + + /** + * 查询单个索引 + *

+ * 查询地址:GET http://{baseUrl}/_cat/indices/{indexName} + */ + public JSONArray getIndices(String indexName) { + StringBuilder urlAfter = new StringBuilder("/indices"); + if (!StringUtils.isEmpty(indexName)) { + urlAfter.append("/").append(indexName.trim().toLowerCase()); + } + return cat(urlAfter.toString(), JSONArray.class).getBody(); + } + + /** + * 索引是否存在 + */ + public boolean indexExists(String indexName) { + try { + JSONArray array = getIndices(indexName); + return array != null; + } catch (org.springframework.web.client.HttpClientErrorException ex) { + if (HttpStatus.NOT_FOUND == ex.getStatusCode()) { + return false; + } else { + throw ex; + } + } + } + + /** + * 根据ID获取索引数据,未查询到返回null + *

+ * 查询地址:GET http://{baseUrl}/{indexName}/{typeName}/{dataId} + * + * @param indexName 索引名称 + * @param typeName type,一个任意字符串,用于分类 + * @param dataId 数据id + * @return + */ + public JSONObject getDataById(String indexName, String typeName, String dataId) { + String url = this.getBaseUrl(indexName, typeName).append("/").append(dataId).toString(); + log.info("url:" + url); + JSONObject result = RestUtil.get(url); + boolean found = result.getBoolean("found"); + if (found) { + return result.getJSONObject("_source"); + } else { + return null; + } + } + + /** + * 创建索引 + *

+ * 查询地址:PUT http://{baseUrl}/{indexName} + */ + public boolean createIndex(String indexName) { + String url = this.getBaseUrl(indexName).toString(); + + /* 返回结果 (仅供参考) + "createIndex": { + "shards_acknowledged": true, + "acknowledged": true, + "index": "hello_world" + } + */ + try { + return RestUtil.put(url).getBoolean("acknowledged"); + } catch (org.springframework.web.client.HttpClientErrorException ex) { + if (HttpStatus.BAD_REQUEST == ex.getStatusCode()) { + log.warn("索引创建失败:" + indexName + " 已存在,无需再创建"); + } else { + ex.printStackTrace(); + } + } + return false; + } + + /** + * 删除索引 + *

+ * 查询地址:DELETE http://{baseUrl}/{indexName} + */ + public boolean removeIndex(String indexName) { + String url = this.getBaseUrl(indexName).toString(); + try { + return RestUtil.delete(url).getBoolean("acknowledged"); + } catch (org.springframework.web.client.HttpClientErrorException ex) { + if (HttpStatus.NOT_FOUND == ex.getStatusCode()) { + log.warn("索引删除失败:" + indexName + " 不存在,无需删除"); + } else { + ex.printStackTrace(); + } + } + return false; + } + + /** + * 获取索引字段映射(可获取字段类型) + *

+ * + * @param indexName 索引名称 + * @param typeName 分类名称 + * @return + */ + public JSONObject getIndexMapping(String indexName, String typeName) { + String url = this.getBaseUrl(indexName, typeName).append("/_mapping?").append(FORMAT_JSON).toString(); + // 针对 es 7.x 版本做兼容 + this.getElasticsearchVersion(); + if (StringUtils.isNotEmpty(this.version) && this.version.startsWith(IE_SEVEN)) { + url += "&include_type_name=true"; + } + log.info("getIndexMapping-url:" + url); + /* + * 参考返回JSON结构: + * + *{ + * // 索引名称 + * "[indexName]": { + * "mappings": { + * // 分类名称 + * "[typeName]": { + * "properties": { + * // 字段名 + * "input_number": { + * // 字段类型 + * "type": "long" + * }, + * "input_string": { + * "type": "text", + * "fields": { + * "keyword": { + * "type": "keyword", + * "ignore_above": 256 + * } + * } + * } + * } + * } + * } + * } + * } + */ + try { + return RestUtil.get(url); + } catch (org.springframework.web.client.HttpClientErrorException e) { + String message = e.getMessage(); + if (message != null && message.contains(URL_NOT_FOUND)) { + return null; + } + throw e; + } + } + + /** + * 获取索引字段映射,返回Java实体类 + * + * @param indexName + * @param typeName + * @return + */ + public Map getIndexMappingFormat(String indexName, String typeName, Class clazz) { + JSONObject mapping = this.getIndexMapping(indexName, typeName); + Map map = new HashMap<>(5); + if (mapping == null) { + return map; + } + // 获取字段属性 + JSONObject properties = mapping.getJSONObject(indexName) + .getJSONObject("mappings") + .getJSONObject(typeName) + .getJSONObject("properties"); + // 封装成 java类型 + for (String key : properties.keySet()) { + T entity = properties.getJSONObject(key).toJavaObject(clazz); + map.put(key, entity); + } + return map; + } + + /** + * 保存数据,详见:saveOrUpdate + */ + public boolean save(String indexName, String typeName, String dataId, JSONObject data) { + return this.saveOrUpdate(indexName, typeName, dataId, data); + } + + /** + * 更新数据,详见:saveOrUpdate + */ + public boolean update(String indexName, String typeName, String dataId, JSONObject data) { + return this.saveOrUpdate(indexName, typeName, dataId, data); + } + + /** + * 保存或修改索引数据 + *

+ * 查询地址:PUT http://{baseUrl}/{indexName}/{typeName}/{dataId} + * + * @param indexName 索引名称 + * @param typeName type,一个任意字符串,用于分类 + * @param dataId 数据id + * @param data 要存储的数据 + * @return + */ + public boolean saveOrUpdate(String indexName, String typeName, String dataId, JSONObject data) { + String url = this.getBaseUrl(indexName, typeName).append("/").append(dataId).append("?refresh=wait_for").toString(); + /* 返回结果(仅供参考) + "createIndexA2": { + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 0, + "_index": "test_index_1", + "_type": "test_type_1", + "_id": "a2", + "_version": 1, + "_primary_term": 1 + } + */ + + try { + // 去掉 data 中为空的值 + Set keys = data.keySet(); + List emptyKeys = new ArrayList<>(keys.size()); + for (String key : keys) { + String value = data.getString(key); + //1、剔除空值 + if (StringUtils.isEmpty(value) || "[]".equals(value)) { + emptyKeys.add(key); + } + //2、剔除上传控件值(会导致ES同步失败,报异常failed to parse field [ge_pic] of type [text] ) + if (StringUtils.isNotEmpty(value) && value.indexOf("[{") != -1) { + emptyKeys.add(key); + log.info("-------剔除上传控件字段------------key: " + key); + } + } + for (String key : emptyKeys) { + data.remove(key); + } + } catch (Exception e) { + e.printStackTrace(); + } + try { + String result = RestUtil.put(url, data).getString("result"); + return "created".equals(result) || "updated".equals(result); + } catch (Exception e) { + log.error(e.getMessage() + "\n-- url: " + url + "\n-- data: " + data.toJSONString()); + //TODO 打印接口返回异常json + return false; + } + } + + /** + * 批量保存数据 + * + * @param indexName 索引名称 + * @param typeName type,一个任意字符串,用于分类 + * @param dataList 要存储的数据数组,每行数据必须包含id + * @return + */ + public boolean saveBatch(String indexName, String typeName, JSONArray dataList) { + String url = this.getBaseUrl().append("/_bulk").append("?refresh=wait_for").toString(); + StringBuilder bodySb = new StringBuilder(); + for (int i = 0; i < dataList.size(); i++) { + JSONObject data = dataList.getJSONObject(i); + String id = data.getString("id"); + // 该行的操作 + // {"create": {"_id":"${id}", "_index": "${indexName}", "_type": "${typeName}"}} + JSONObject action = new JSONObject(); + JSONObject actionInfo = new JSONObject(); + actionInfo.put("_id", id); + actionInfo.put("_index", indexName); + actionInfo.put("_type", typeName); + action.put("create", actionInfo); + bodySb.append(action.toJSONString()).append("\n"); + // 该行的数据 + data.remove("id"); + bodySb.append(data.toJSONString()).append("\n"); + } + System.out.println("+-+-+-: bodySb.toString(): " + bodySb.toString()); + HttpHeaders headers = RestUtil.getHeaderApplicationJson(); + RestUtil.request(url, HttpMethod.PUT, headers, null, bodySb, JSONObject.class); + return true; + } + + /** + * 删除索引数据 + *

+ * 请求地址:DELETE http://{baseUrl}/{indexName}/{typeName}/{dataId} + */ + public boolean delete(String indexName, String typeName, String dataId) { + String url = this.getBaseUrl(indexName, typeName).append("/").append(dataId).toString(); + /* 返回结果(仅供参考) + { + "_index": "es_demo", + "_type": "docs", + "_id": "001", + "_version": 3, + "result": "deleted", + "_shards": { + "total": 1, + "successful": 1, + "failed": 0 + }, + "_seq_no": 28, + "_primary_term": 18 + } + */ + try { + return "deleted".equals(RestUtil.delete(url).getString("result")); + } catch (org.springframework.web.client.HttpClientErrorException ex) { + if (HttpStatus.NOT_FOUND == ex.getStatusCode()) { + return false; + } else { + throw ex; + } + } + } + + + /* = = = 以下关于查询和查询条件的方法 = = =*/ + + /** + * 查询数据 + *

+ * 请求地址:POST http://{baseUrl}/{indexName}/{typeName}/_search + */ + public JSONObject search(String indexName, String typeName, JSONObject queryObject) { + String url = this.getBaseUrl(indexName, typeName).append("/_search").toString(); + + log.info("url:" + url + " ,search: " + queryObject.toJSONString()); + JSONObject res = RestUtil.post(url, queryObject); + log.info("url:" + url + " ,return res: \n" + res.toJSONString()); + return res; + } + + /** + * @param source (源滤波器)指定返回的字段,传null返回所有字段 + * @param query + * @param from 从第几条数据开始 + * @param size 返回条目数 + * @return { "query": query } + */ + public JSONObject buildQuery(List source, JSONObject query, int from, int size) { + JSONObject json = new JSONObject(); + if (source != null) { + json.put("_source", source); + } + json.put("query", query); + json.put("from", from); + json.put("size", size); + return json; + } + + /** + * @return { "bool" : { "must": must, "must_not": mustNot, "should": should } } + */ + public JSONObject buildBoolQuery(JSONArray must, JSONArray mustNot, JSONArray should) { + JSONObject bool = new JSONObject(); + if (must != null) { + bool.put("must", must); + } + if (mustNot != null) { + bool.put("must_not", mustNot); + } + if (should != null) { + bool.put("should", should); + } + JSONObject json = new JSONObject(); + json.put("bool", bool); + return json; + } + + /** + * @param field 要查询的字段 + * @param args 查询参数,参考: *哈哈* OR *哒* NOT *呵* OR *啊* + * @return + */ + public JSONObject buildQueryString(String field, String... args) { + if (field == null) { + return null; + } + StringBuilder sb = new StringBuilder(field).append(":("); + if (args != null) { + for (String arg : args) { + sb.append(arg).append(" "); + } + } + sb.append(")"); + return this.buildQueryString(sb.toString()); + } + + /** + * @return { "query_string": { "query": query } } + */ + public JSONObject buildQueryString(String query) { + JSONObject queryString = new JSONObject(); + queryString.put("query", query); + JSONObject json = new JSONObject(); + json.put("query_string", queryString); + return json; + } + + /** + * @param field 查询字段 + * @param min 最小值 + * @param max 最大值 + * @param containMin 范围内是否包含最小值 + * @param containMax 范围内是否包含最大值 + * @return { "range" : { field : { 『 "gt『e』?containMin" : min 』?min!=null , 『 "lt『e』?containMax" : max 』}} } + */ + public JSONObject buildRangeQuery(String field, Object min, Object max, boolean containMin, boolean containMax) { + JSONObject inner = new JSONObject(); + if (min != null) { + if (containMin) { + inner.put("gte", min); + } else { + inner.put("gt", min); + } + } + if (max != null) { + if (containMax) { + inner.put("lte", max); + } else { + inner.put("lt", max); + } + } + JSONObject range = new JSONObject(); + range.put(field, inner); + JSONObject json = new JSONObject(); + json.put("range", range); + return json; + } + +} + diff --git a/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/core/QueryStringBuilder.java b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/core/QueryStringBuilder.java new file mode 100644 index 0000000000..590d60092a --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/core/QueryStringBuilder.java @@ -0,0 +1,98 @@ +package cn.iocoder.yudao.framework.elasticsearch.core; + +/** + * 用于创建 ElasticSearch 的 queryString + * + * @author sunjianlei + */ +public class QueryStringBuilder { + + StringBuilder builder; + + public QueryStringBuilder(String field, String str, boolean not, boolean addQuot) { + builder = this.createBuilder(field, str, not, addQuot); + } + + public QueryStringBuilder(String field, String str, boolean not) { + builder = this.createBuilder(field, str, not, true); + } + + /** + * 创建 StringBuilder + * + * @param field + * @param str + * @param not 是否是不匹配 + * @param addQuot 是否添加双引号 + * @return + */ + public StringBuilder createBuilder(String field, String str, boolean not, boolean addQuot) { + StringBuilder sb = new StringBuilder(field).append(":("); + if (not) { + sb.append(" NOT "); + } + this.addQuotEffect(sb, str, addQuot); + return sb; + } + + public QueryStringBuilder and(String str) { + return this.and(str, true); + } + + public QueryStringBuilder and(String str, boolean addQuot) { + builder.append(" AND "); + this.addQuot(str, addQuot); + return this; + } + + public QueryStringBuilder or(String str) { + return this.or(str, true); + } + + public QueryStringBuilder or(String str, boolean addQuot) { + builder.append(" OR "); + this.addQuot(str, addQuot); + return this; + } + + public QueryStringBuilder not(String str) { + return this.not(str, true); + } + + public QueryStringBuilder not(String str, boolean addQuot) { + builder.append(" NOT "); + this.addQuot(str, addQuot); + return this; + } + + /** + * 添加双引号(模糊查询,不能加双引号) + */ + private QueryStringBuilder addQuot(String str, boolean addQuot) { + return this.addQuotEffect(this.builder, str, addQuot); + } + + /** + * 是否在两边加上双引号 + * @param builder + * @param str + * @param addQuot + * @return + */ + private QueryStringBuilder addQuotEffect(StringBuilder builder, String str, boolean addQuot) { + if (addQuot) { + builder.append('"'); + } + builder.append(str); + if (addQuot) { + builder.append('"'); + } + return this; + } + + @Override + public String toString() { + return builder.append(")").toString(); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/core/RestUtil.java b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/core/RestUtil.java new file mode 100644 index 0000000000..168273f22a --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/core/RestUtil.java @@ -0,0 +1,233 @@ +package cn.iocoder.yudao.framework.elasticsearch.core; + +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.*; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.Map; + +/** + * 调用 Restful 接口 Util + * + * @author sunjianlei + */ +@Slf4j +public class RestUtil { + + /** + * RestAPI 调用器 + */ + private final static RestTemplate RT; + + static { + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + requestFactory.setConnectTimeout(3000); + requestFactory.setReadTimeout(3000); + RT = new RestTemplate(requestFactory); + // 解决乱码问题 + RT.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); + } + + public static RestTemplate getRestTemplate() { + return RT; + } + + /** + * 发送 get 请求 + */ + public static JSONObject get(String url) { + return getNative(url, null, null).getBody(); + } + + /** + * 发送 get 请求 + */ + public static JSONObject get(String url, JSONObject variables) { + return getNative(url, variables, null).getBody(); + } + + /** + * 发送 get 请求 + */ + public static JSONObject get(String url, JSONObject variables, JSONObject params) { + return getNative(url, variables, params).getBody(); + } + + /** + * 发送 get 请求,返回原生 ResponseEntity 对象 + */ + public static ResponseEntity getNative(String url, JSONObject variables, JSONObject params) { + return request(url, HttpMethod.GET, variables, params); + } + + /** + * 发送 Post 请求 + */ + public static JSONObject post(String url) { + return postNative(url, null, null).getBody(); + } + + /** + * 发送 Post 请求 + */ + public static JSONObject post(String url, JSONObject params) { + return postNative(url, null, params).getBody(); + } + + /** + * 发送 Post 请求 + */ + public static JSONObject post(String url, JSONObject variables, JSONObject params) { + return postNative(url, variables, params).getBody(); + } + + /** + * 发送 POST 请求,返回原生 ResponseEntity 对象 + */ + public static ResponseEntity postNative(String url, JSONObject variables, JSONObject params) { + return request(url, HttpMethod.POST, variables, params); + } + + /** + * 发送 put 请求 + */ + public static JSONObject put(String url) { + return putNative(url, null, null).getBody(); + } + + /** + * 发送 put 请求 + */ + public static JSONObject put(String url, JSONObject params) { + return putNative(url, null, params).getBody(); + } + + /** + * 发送 put 请求 + */ + public static JSONObject put(String url, JSONObject variables, JSONObject params) { + return putNative(url, variables, params).getBody(); + } + + /** + * 发送 put 请求,返回原生 ResponseEntity 对象 + */ + public static ResponseEntity putNative(String url, JSONObject variables, JSONObject params) { + return request(url, HttpMethod.PUT, variables, params); + } + + /** + * 发送 delete 请求 + */ + public static JSONObject delete(String url) { + return deleteNative(url, null, null).getBody(); + } + + /** + * 发送 delete 请求 + */ + public static JSONObject delete(String url, JSONObject variables, JSONObject params) { + return deleteNative(url, variables, params).getBody(); + } + + /** + * 发送 delete 请求,返回原生 ResponseEntity 对象 + */ + public static ResponseEntity deleteNative(String url, JSONObject variables, JSONObject params) { + return request(url, HttpMethod.DELETE, null, variables, params, JSONObject.class); + } + + /** + * 发送请求 + */ + public static ResponseEntity request(String url, HttpMethod method, JSONObject variables, JSONObject params) { + return request(url, method, getHeaderApplicationJson(), variables, params, JSONObject.class); + } + + /** + * 发送请求 + * + * @param url 请求地址 + * @param method 请求方式 + * @param headers 请求头 可空 + * @param variables 请求url参数 可空 + * @param params 请求body参数 可空 + * @param responseType 返回类型 + * @return ResponseEntity + */ + public static ResponseEntity request(String url, HttpMethod method, HttpHeaders headers, JSONObject variables, Object params, Class responseType) { + log.info(" RestUtil --- request --- url = "+ url); + if (StringUtils.isEmpty(url)) { + throw new RuntimeException("url 不能为空"); + } + if (method == null) { + throw new RuntimeException("method 不能为空"); + } + if (headers == null) { + headers = new HttpHeaders(); + } + // 请求体 + String body = ""; + if (params != null) { + if (params instanceof JSONObject) { + body = ((JSONObject) params).toJSONString(); + + } else { + body = params.toString(); + } + } + // 拼接 url 参数 + if (variables != null && !variables.isEmpty()) { + url += ("?" + asUrlVariables(variables)); + } + // 发送请求 + HttpEntity request = new HttpEntity<>(body, headers); + return RT.exchange(url, method, request, responseType); + } + + /** + * 获取JSON请求头 + */ + public static HttpHeaders getHeaderApplicationJson() { + return getHeader(MediaType.APPLICATION_JSON_UTF8_VALUE); + } + + /** + * 获取请求头 + */ + public static HttpHeaders getHeader(String mediaType) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType(mediaType)); + headers.add("Accept", mediaType); + return headers; + } + + /** + * 将 JSONObject 转为 a=1&b=2&c=3...&n=n 的形式 + */ + public static String asUrlVariables(JSONObject variables) { + Map source = variables.getInnerMap(); + Iterator it = source.keySet().iterator(); + StringBuilder urlVariables = new StringBuilder(); + while (it.hasNext()) { + String key = it.next(); + String value = ""; + Object object = source.get(key); + if (object != null) { + if (!StringUtils.isEmpty(object.toString())) { + value = object.toString(); + } + } + urlVariables.append("&").append(key).append("=").append(value); + } + // 去掉第一个& + return urlVariables.substring(1); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/dataobject/ProductInfo.java b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/dataobject/ProductInfo.java new file mode 100644 index 0000000000..e517fb0284 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/dataobject/ProductInfo.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.framework.elasticsearch.dataobject; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ProductInfo { + private String productName; + private String productDescription; + private String color; + private Integer price; +} + diff --git a/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/package-info.java b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/package-info.java new file mode 100644 index 0000000000..b534612f86 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/java/cn/iocoder/yudao/framework/elasticsearch/package-info.java @@ -0,0 +1,4 @@ +/** + * 采用 Spring Data Redis 操作 Redis,底层使用 Redisson 作为客户端 + */ +package cn.iocoder.yudao.framework.elasticsearch; diff --git a/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..02d4a68353 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration +cn.iocoder.yudao.framework.redis.config.YudaoCacheAutoConfiguration \ No newline at end of file diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java index 76c3b5225b..8bd22d8e16 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java @@ -44,8 +44,8 @@ public class TenantController { @Operation(summary = "使用租户名,获得租户编号", description = "登录界面,根据用户的租户名,获得租户编号") @Parameter(name = "name", description = "租户名", required = true, example = "1024") public CommonResult getTenantIdByName(@RequestParam("name") String name) { - TenantDO tenant = tenantService.getTenantByName(name); - return success(tenant != null ? tenant.getId() : null); + Long tenantId = tenantService.getTenantByUserName(name); + return success(tenantId); } @GetMapping({ "simple-list" }) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantRespVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantRespVO.java index 5a444b5213..d7bf6690f5 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantRespVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantRespVO.java @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; +import java.util.Set; @Schema(description = "管理后台 - 租户 Response VO") @Data @@ -40,7 +41,7 @@ public class TenantRespVO { private String website; @Schema(description = "租户套餐编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long packageId; + private Set packageId; @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime expireTime; diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java index 117d365ff5..e023c720c3 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java @@ -11,6 +11,7 @@ import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import java.time.LocalDateTime; +import java.util.Set; @Schema(description = "管理后台 - 租户创建/修改 Request VO") @Data @@ -39,7 +40,7 @@ public class TenantSaveReqVO { @Schema(description = "租户套餐编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @NotNull(message = "租户套餐编号不能为空") - private Long packageId; + private Set packageId; @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "过期时间不能为空") diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantDO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantDO.java index 6e60f614b5..8af03b5ee3 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantDO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantDO.java @@ -5,10 +5,13 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.*; import java.time.LocalDateTime; +import java.util.Set; /** * 租户 DO @@ -39,6 +42,32 @@ public class TenantDO extends BaseDO { * 租户名,唯一 */ private String name; + /** + * 门头 + */ + private String doorHeaderImage; + /** + * 类型 + *

+ * 枚举 {@link TODO convenience_store_type 对应的类} + */ + private String storeType; + /** + * 省市区 + */ + private Integer areaId; + /** + * 营业执照 + */ + private String businessLicence; + /** + * 审批状态 + */ + private Integer approveStatus; + /** + * 审批备注 + */ + private String approveRemark; /** * 联系人的用户编号 * @@ -69,7 +98,8 @@ public class TenantDO extends BaseDO { * 关联 {@link TenantPackageDO#getId()} * 特殊逻辑:系统内置租户,不使用套餐,暂时使用 {@link #PACKAGE_ID_SYSTEM} 标识 */ - private Long packageId; + @TableField(typeHandler = JacksonTypeHandler.class) + private Set packageId; /** * 过期时间 */ diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageService.java index 1abe4feebd..96dfe223fe 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageService.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO; import javax.validation.Valid; import java.util.List; +import java.util.Set; /** * 租户套餐 Service 接口 @@ -59,7 +60,7 @@ public interface TenantPackageService { * @param id 编号 * @return 租户套餐 */ - TenantPackageDO validTenantPackage(Long id); + List validTenantPackage(Set ids); /** * 获得指定状态的租户套餐列表 diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java index 8fccc64d4c..248b3d4936 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java @@ -17,7 +17,9 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.util.ArrayList; import java.util.List; +import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @@ -101,15 +103,19 @@ public class TenantPackageServiceImpl implements TenantPackageService { } @Override - public TenantPackageDO validTenantPackage(Long id) { - TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id); - if (tenantPackage == null) { - throw exception(TENANT_PACKAGE_NOT_EXISTS); + public List validTenantPackage(Set ids) { + List tenantPackageDOS = new ArrayList<>(); + for(Long id :ids){ + TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id); + if (tenantPackage == null) { + throw exception(TENANT_PACKAGE_NOT_EXISTS); + } + if (tenantPackage.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { + throw exception(TENANT_PACKAGE_DISABLE, tenantPackage.getName()); + } + tenantPackageDOS.add(tenantPackage); } - if (tenantPackage.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { - throw exception(TENANT_PACKAGE_DISABLE, tenantPackage.getName()); - } - return tenantPackage; + return tenantPackageDOS; } @Override diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java index 96af6bc14a..c8bf7f8385 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java @@ -73,6 +73,14 @@ public interface TenantService { */ TenantDO getTenantByName(String name); + /** + * 获得名字对应的租户 + * + * @param userName 租户名 + * @return 租户 + */ + Long getTenantByUserName(String userName); + /** * 获得域名对应的租户 * diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java index 956b957150..5f1227081a 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java @@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper; import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum; @@ -38,6 +39,7 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -104,7 +106,7 @@ public class TenantServiceImpl implements TenantService { // 校验租户域名是否重复 validTenantWebsiteDuplicate(createReqVO.getWebsite(), null); // 校验套餐被禁用 - TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId()); + List tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId()); // 创建租户 TenantDO tenant = BeanUtils.toBean(createReqVO, TenantDO.class); @@ -129,14 +131,16 @@ public class TenantServiceImpl implements TenantService { return userId; } - private Long createRole(TenantPackageDO tenantPackage) { + private Long createRole(List tenantPackage) { // 创建角色 RoleSaveReqVO reqVO = new RoleSaveReqVO(); reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode()) .setSort(0).setRemark("系统自动生成"); Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType()); // 分配权限 - permissionService.assignRoleMenu(roleId, tenantPackage.getMenuIds()); + for(TenantPackageDO tenantPackageDO : tenantPackage){ + permissionService.assignRoleMenu(roleId, tenantPackageDO.getMenuIds()); + } return roleId; } @@ -150,14 +154,18 @@ public class TenantServiceImpl implements TenantService { // 校验租户域名是否重复 validTenantWebsiteDuplicate(updateReqVO.getWebsite(), updateReqVO.getId()); // 校验套餐被禁用 - TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId()); + List tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId()); + Set newMenuIds = new HashSet<>(); + for(TenantPackageDO tenantPackageDO : tenantPackage){ + newMenuIds.addAll(tenantPackageDO.getMenuIds()); + } // 更新租户 TenantDO updateObj = BeanUtils.toBean(updateReqVO, TenantDO.class); tenantMapper.updateById(updateObj); // 如果套餐发生变化,则修改其角色的权限 if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) { - updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds()); + updateTenantRoleMenu(tenant.getId(), newMenuIds); } } @@ -252,6 +260,12 @@ public class TenantServiceImpl implements TenantService { return tenantMapper.selectByName(name); } + @Override + public Long getTenantByUserName(String userName) { + AdminUserDO userDO = userService.getUserByUsername(userName); + return userDO != null ? userDO.getTenantId() : null; + } + @Override public TenantDO getTenantByWebsite(String website) { return tenantMapper.selectByWebsite(website); @@ -292,18 +306,21 @@ public class TenantServiceImpl implements TenantService { } // 获得租户,然后获得菜单 TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId()); - Set menuIds; + Set menuIds = new HashSet<>(); if (isSystemTenant(tenant)) { // 系统租户,菜单是全量的 menuIds = CollectionUtils.convertSet(menuService.getMenuList(), MenuDO::getId); } else { - menuIds = tenantPackageService.getTenantPackage(tenant.getPackageId()).getMenuIds(); + for(Long packageId : tenant.getPackageId()){ + Set tempMenuIds = tenantPackageService.getTenantPackage(packageId).getMenuIds(); + menuIds.addAll(tempMenuIds); + } } // 执行处理器 handler.handle(menuIds); } private static boolean isSystemTenant(TenantDO tenant) { - return Objects.equals(tenant.getPackageId(), TenantDO.PACKAGE_ID_SYSTEM); + return tenant.getPackageId().contains(TenantDO.PACKAGE_ID_SYSTEM); } private boolean isTenantDisable() { diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index 97ee0daf38..4b1681911b 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -33,73 +33,73 @@ - - - - - + + cn.iocoder.boot + yudao-module-member + ${revision} + - - - - - + + cn.iocoder.boot + yudao-module-report + ${revision} + - - - - - + + cn.iocoder.boot + yudao-module-bpm + ${revision} + - - - - - + + cn.iocoder.boot + yudao-module-pay + ${revision} + - - - - - + + cn.iocoder.boot + yudao-module-mp + ${revision} + - - - - - - - - - - - - - - - - - - - - + + cn.iocoder.boot + yudao-module-product + ${revision} + + + cn.iocoder.boot + yudao-module-promotion + ${revision} + + + cn.iocoder.boot + yudao-module-trade + ${revision} + + + cn.iocoder.boot + yudao-module-statistics + ${revision} + - - - - - + + cn.iocoder.boot + yudao-module-crm + ${revision} + - - - - - + + cn.iocoder.boot + yudao-module-erp + ${revision} + diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 6501a5292a..e9ee8e2db0 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -3,6 +3,7 @@ server: --- #################### 数据库相关配置 #################### spring: + # 数据源配置项 autoconfigure: # noinspection SpringBootApplicationYaml exclude: @@ -11,8 +12,8 @@ spring: - de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置 - de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置 - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置 - - org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建 - - org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建 + # - org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建 + # - org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建 # 数据源配置项 datasource: druid: # Druid 【监控】相关的全局配置 @@ -49,7 +50,7 @@ spring: primary: master datasource: master: - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + url: jdbc:mysql://192.168.248.128:3306/new-ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 @@ -65,25 +66,25 @@ spring: # password: SYSDBA001 # DM 连接的示例 # username: root # OpenGauss 连接的示例 # password: Yudao@2024 # OpenGauss 连接的示例 - slave: # 模拟从库,可根据自己需要修改 - lazy: true # 开启懒加载,保证启动速度 - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true - username: root - password: 123456 -# tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!) -# url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro -# driver-class-name: com.taosdata.jdbc.rs.RestfulDriver -# username: root -# password: taosdata -# druid: -# validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL + # slave: # 模拟从库,可根据自己需要修改 + # lazy: true # 开启懒加载,保证启动速度 + # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true + # username: root + # password: 123456 + # tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!) + # url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro + # driver-class-name: com.taosdata.jdbc.rs.RestfulDriver + # username: root + # password: taosdata + # druid: + # validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 redis: - host: 127.0.0.1 # 地址 + host: 192.168.248.128 # 地址 port: 6379 # 端口 database: 0 # 数据库索引 -# password: dev # 密码,建议生产环境开启 + password: 123456 # 密码,建议生产环境开启 --- #################### 定时任务相关配置 #################### @@ -194,10 +195,10 @@ debug: false --- #################### 微信公众号、小程序相关配置 #################### wx: mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档 -# app-id: wx041349c6f39b268b # 测试号(牛希尧提供的) -# secret: 5abee519483bc9f8cb37ce280e814bd0 -# app-id: wx5b23ba7a5589ecbb # 测试号(自己的) -# secret: 2a7b3b20c537e52e74afd395eb85f61f + # app-id: wx041349c6f39b268b # 测试号(牛希尧提供的) + # secret: 5abee519483bc9f8cb37ce280e814bd0 + # app-id: wx5b23ba7a5589ecbb # 测试号(自己的) + # secret: 2a7b3b20c537e52e74afd395eb85f61f app-id: wxf56b1542b9e85f8a # 测试号(Kongdy 提供的) secret: 496379dcef1ba869e9234de8d598cfd3 # 存储配置,解决 AccessToken 的跨节点的共享 @@ -208,12 +209,12 @@ wx: miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档 # appid: wx62056c0d5e8db250 # 测试号(牛希尧提供的) # secret: 333ae72f41552af1e998fe1f54e1584a -# appid: wx63c280fe3248a3e7 # wenhualian的接口测试号 -# secret: 6f270509224a7ae1296bbf1c8cb97aed + # appid: wx63c280fe3248a3e7 # wenhualian的接口测试号 + # secret: 6f270509224a7ae1296bbf1c8cb97aed appid: wxc4598c446f8a9cb3 # 测试号(Kongdy 提供的) secret: 4a1a04e07f6a4a0751b39c3064a92c8b -# appid: wx66186af0759f47c9 # 测试号(puhui 提供的) -# secret: 3218bcbd112cbc614c7264ceb20144ac + # appid: wx66186af0759f47c9 # 测试号(puhui 提供的) + # secret: 3218bcbd112cbc614c7264ceb20144ac config-storage: type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 key-prefix: wa # Redis Key 的前缀 @@ -269,5 +270,5 @@ justauth: --- #################### iot相关配置 TODO 芋艿【IOT】:再瞅瞅 #################### pf4j: -# pluginsDir: /tmp/ + # pluginsDir: /tmp/ pluginsDir: ../plugins \ No newline at end of file