正在显示
1 个修改的文件
包含
347 行增加
和
223 行删除
1 | -/************************************************************************** | ||
2 | -* file: dmpservermanager.cpp | ||
3 | - | ||
4 | -* Author: wanzhongping | ||
5 | -* Date: 2021-07-27 21:59:46 | ||
6 | -* Email: zhongpingw@chinadci.com | ||
7 | -* copyright: 广州城市信息研究所有限公司 | ||
8 | -***************************************************************************/ | ||
9 | -#include "dmpservermanager.h" | ||
10 | -#include "dmpserver.h" | ||
11 | -#include "dmphttputils.h" | ||
12 | -#include "dmpserverConfig.h" | ||
13 | -#include <memory> | ||
14 | -#include <boost/property_tree/ptree.hpp> | ||
15 | -#include <boost/property_tree/json_parser.hpp> | ||
16 | -#include <boost/property_tree/xml_parser.hpp> | ||
17 | -#include <sstream> | ||
18 | -#include <fstream> | ||
19 | -#include <math.h> | ||
20 | -#include "dmptilethumbnail.h" | ||
21 | -#include "dmptilelayer.h" | ||
22 | - | ||
23 | -<<<<<<< Updated upstream | ||
24 | - | ||
25 | -#include <iostream> | ||
26 | -#include "dmplogger.h" | ||
27 | - | ||
28 | -#include <math.h> | ||
29 | -#include "dmptilethumbnail.h" | ||
30 | -#include "dmptilelayer.h" | ||
31 | -======= | ||
32 | ->>>>>>> Stashed changes | ||
33 | - | ||
34 | - | ||
35 | - | ||
36 | -DmpServerManager::DmpServerManager() | ||
37 | -{ | ||
38 | - serverRegistry_ = new DmpServerRegistry(); | ||
39 | -} | ||
40 | - | ||
41 | -DmpServerManager::~DmpServerManager() | ||
42 | -{ | ||
43 | - if (serverRegistry_) | ||
44 | - { | ||
45 | - delete serverRegistry_; | ||
46 | - serverRegistry_ = NULL; | ||
47 | - } | ||
48 | - | ||
49 | - ProjectMap::iterator iter = projects_.begin(); | ||
50 | - for (; iter != projects_.end(); ++iter) | ||
51 | - { | ||
52 | - DmpProject *mapProject = iter->second; | ||
53 | - if (mapProject) | ||
54 | - { | ||
55 | - delete mapProject; | ||
56 | - mapProject = nullptr; | ||
57 | - } | ||
58 | - } | ||
59 | - projects_.clear(); | ||
60 | -} | ||
61 | - | ||
62 | -void DmpServerManager::init(const boost::filesystem::path &modulePath) | ||
63 | -{ | ||
64 | - serverRegistry_->init(modulePath); | ||
65 | -<<<<<<< Updated upstream | ||
66 | - if(!loadServices()) | ||
67 | - { | ||
68 | - std::cout << "加载服务失败!" << std::endl; | ||
69 | - LOGGER_ERROR("加载服务失败!"); | ||
70 | - } | ||
71 | - //LoadDmpServices(); | ||
72 | -} | ||
73 | - | ||
74 | -std::string DmpServerManager::getCapabilities() | ||
75 | -{ | ||
76 | - return serverRegistry_->getCapabilities(); | ||
77 | -} | ||
78 | - | ||
79 | -std::shared_ptr<DmpServer> DmpServerManager::serverForRequest(const DmpServerRequest &request) | ||
80 | -{ | ||
81 | - return serverRegistry_->getServerForRequest(request); | ||
82 | -} | ||
83 | - | ||
84 | -std::shared_ptr<DmpServerApi> DmpServerManager::apiForRequest(const DmpServerRequest &request) | ||
85 | -{ | ||
86 | - return serverRegistry_->getApiForRequest(request); | ||
87 | -} | ||
88 | - | ||
89 | -DmpProject *DmpServerManager::getProject(const std::string &serviceName) | ||
90 | -{ | ||
91 | - std::map<std::string, DmpProject *>::iterator iter = projects_.find(serviceName); | ||
92 | - if (iter != projects_.end()) | ||
93 | - { | ||
94 | - return iter->second; | ||
95 | - } | ||
96 | - else | ||
97 | - { | ||
98 | - return nullptr; | ||
99 | - } | ||
100 | -} | ||
101 | - | ||
102 | -bool DmpServerManager::removeProject(const std::string &serviceName) | ||
103 | -{ | ||
104 | - try | ||
105 | - { | ||
106 | - std::map<std::string, DmpProject *>::iterator iter = projects_.find(serviceName); | ||
107 | - if (iter != projects_.end()) | ||
108 | - { | ||
109 | - delete iter->second; | ||
110 | - projects_.erase(iter); | ||
111 | - } | ||
112 | - } | ||
113 | - catch (const std::exception &e) | ||
114 | - { | ||
115 | - std::cerr << e.what() << '\n'; | ||
116 | - return false; | ||
117 | - } | ||
118 | - return true; | ||
119 | -} | ||
120 | - | ||
121 | -bool DmpServerManager::publish(const std::string& serverName, const std::string& serviceName, const std::string& title, int capabilities, const std::string& projectData) | ||
122 | -{ | ||
123 | - //project | ||
124 | - std::string projData; | ||
125 | - if (!DmpServerUtils::Base64Decode(projectData, &projData)) | ||
126 | - { | ||
127 | - return false; | ||
128 | - } | ||
129 | - DmpProject *project = new DmpProject(); | ||
130 | - if (!project->Read(projData)) | ||
131 | - { | ||
132 | - delete project; | ||
133 | - return false; | ||
134 | - } | ||
135 | - | ||
136 | - if (!serverRegistry_->getServer(serverName)->publish(serviceName, title, capabilities, *project)) | ||
137 | - { | ||
138 | - delete project; | ||
139 | - return false; | ||
140 | - } | ||
141 | - projects_[serviceName] = project; | ||
142 | - //增加缩略图处理 | ||
143 | - if(serverName=="tileserver") | ||
144 | - { | ||
145 | - DmpTileLayer *tileLayer = static_cast<DmpTileLayer *>(project->getLayer()); | ||
146 | - DmpTileThumbnail::ctreatWmtsThumbnail(tileLayer); | ||
147 | - } | ||
148 | - | ||
149 | - return true; | ||
150 | -} | ||
151 | - | ||
152 | -bool DmpServerManager::deleteService(const std::string &serverName, const std::string &serviceName) | ||
153 | -{ | ||
154 | - if (serverRegistry_->getServer(serverName)->remove(serviceName) && removeProject(serviceName)) | ||
155 | - { | ||
156 | - return true; | ||
157 | - } | ||
158 | - else | ||
159 | - { | ||
160 | - return false; | ||
161 | - } | ||
162 | -} | ||
163 | - | ||
164 | -bool DmpServerManager::startService(const std::string &serverName, const std::string &serviceName) | ||
165 | -{ | ||
166 | - return serverRegistry_->getServer(serverName)->start(serviceName); | ||
167 | -} | ||
168 | - | ||
169 | -bool DmpServerManager::stopService(const std::string &serverName, const std::string &serviceName) | ||
170 | -{ | ||
171 | - return serverRegistry_->getServer(serverName)->stop(serviceName); | ||
172 | -} | ||
173 | -bool DmpServerManager::loadServices() | ||
174 | -{ | ||
175 | - boost::property_tree::ptree pt,ptList; | ||
176 | - std::string conn = DmpServerConfig::Instance()->getMetaUrl(); | ||
177 | - const std::string url= conn + URI_RELOAD; | ||
178 | - std::string strContent=DmpHttp::get(url); | ||
179 | - if(strContent.length()==0) | ||
180 | - { | ||
181 | - return false; | ||
182 | - } | ||
183 | - std::stringstream ssData; | ||
184 | - ssData<<strContent.c_str(); | ||
185 | - boost::property_tree::read_json(ssData, pt); | ||
186 | - int iCount = std::atoi(pt.get<std::string>("data.count").c_str()); | ||
187 | - if(iCount>0) | ||
188 | - { | ||
189 | - ptList=pt.get_child("data.list"); | ||
190 | - for (auto& e : ptList) | ||
191 | - { | ||
192 | - std::string name = e.second.get<std::string>("name"); | ||
193 | - std::string title = e.second.get<std::string>("title"); | ||
194 | - std::string type = e.second.get<std::string>("type"); | ||
195 | - int capabilities =e.second.get<int>("capabilities"); | ||
196 | - std::string project = e.second.get<std::string>("project"); | ||
197 | - this->initServices(type,name,title,capabilities,project); | ||
198 | - } | ||
199 | - } | ||
200 | - return true; | ||
201 | -} | ||
202 | -bool DmpServerManager::initServices(const std::string& serverName, const std::string& serviceName, const std::string& title, int capabilities, const std::string& projectData) | ||
203 | -{ | ||
204 | - //project | ||
205 | - std::string projData; | ||
206 | - if (!DmpServerUtils::Base64Decode(projectData, &projData)) | ||
207 | - { | ||
208 | - return false; | ||
209 | - } | ||
210 | - DmpProject *project = new DmpProject(); | ||
211 | - if (!project->Read(projData)) | ||
212 | - { | ||
213 | - delete project; | ||
214 | - return false; | ||
215 | - } | ||
216 | - | ||
217 | - if (!serverRegistry_->getServer(serverName)->publish(serviceName, title, capabilities, *project)) | ||
218 | - { | ||
219 | - delete project; | ||
220 | - return false; | ||
221 | - } | ||
222 | - projects_[serviceName] = project; | ||
223 | - return true; | 1 | +/************************************************************************** |
2 | +* file: dmpservermanager.cpp | ||
3 | + | ||
4 | +* Author: wanzhongping | ||
5 | +* Date: 2021-07-27 21:59:46 | ||
6 | +* Email: zhongpingw@chinadci.com | ||
7 | +* copyright: 广州城市信息研究所有限公司 | ||
8 | +***************************************************************************/ | ||
9 | +#include "dmpservermanager.h" | ||
10 | +#include <boost/format.hpp> | ||
11 | +#include <boost/algorithm/string.hpp> | ||
12 | +#include <boost/regex.hpp> | ||
13 | +#include <boost/lexical_cast.hpp> | ||
14 | + | ||
15 | +#include "dmpserver.h" | ||
16 | + | ||
17 | +using namespace std; | ||
18 | +namespace | ||
19 | +{ | ||
20 | + | ||
21 | + string MakeServerKey(const string &name, const string &version) | ||
22 | + { | ||
23 | + boost::format fmt = boost::format("%1%_%2%") % name % version; | ||
24 | + return fmt.str(); | ||
25 | + } | ||
26 | + | ||
27 | + bool IsVersionGreater(const string &v1, const string &v2) | ||
28 | + { | ||
29 | + vector<string> vecSeg1; | ||
30 | + boost::split(vecSeg1, v1, boost::is_any_of("."), boost::token_compress_on); | ||
31 | + vector<string> vecSeg2; | ||
32 | + boost::split(vecSeg2, v2, boost::is_any_of("."), boost::token_compress_on); | ||
33 | + | ||
34 | + vector<string>::iterator it1 = vecSeg1.begin(); | ||
35 | + vector<string>::iterator it2 = vecSeg2.begin(); | ||
36 | + bool isint; | ||
37 | + while (it1 != vecSeg1.end() && it2 != vecSeg2.end()) | ||
38 | + { | ||
39 | + if (*it1 != *it2) | ||
40 | + { | ||
41 | + // Compare as numbers | ||
42 | + int i1 = atoi(it1->c_str()); | ||
43 | + int i2 = atoi(it2->c_str()); | ||
44 | + if (i1 != i2) | ||
45 | + { | ||
46 | + return i1 > i2; | ||
47 | + } | ||
48 | + } | ||
49 | + ++it1; | ||
50 | + ++it2; | ||
51 | + } | ||
52 | + return false; | ||
53 | + } | ||
54 | +} // namespace | ||
55 | + | ||
56 | +DmpServerManager::DmpServerManager(const std::string& root_path) | ||
57 | + :root_path_(root_path) | ||
58 | +{ | ||
59 | + | ||
60 | +} | ||
61 | + | ||
62 | +DmpServerManager::~DmpServerManager() | ||
63 | +{ | ||
64 | + CleanUp(); | ||
65 | + std::cout << "Destructing DmpServerManager" << std::endl; | ||
66 | +} | ||
67 | + | ||
68 | +void DmpServerManager::Init(const boost::filesystem::path &module_path) | ||
69 | +{ | ||
70 | + nativeLoader_.LoadModules(module_path, *this); | ||
71 | + | ||
72 | + LoadServices(); | ||
73 | +} | ||
74 | + | ||
75 | +int DmpServerManager::GetServicePaths(const std::string& service_dir, std::vector<std::string>& service_paths) | ||
76 | +{ | ||
77 | + if (!boost::filesystem::exists(service_dir)) | ||
78 | + { | ||
79 | + return -1; | ||
80 | + } | ||
81 | + boost::filesystem::directory_iterator end_iter; | ||
82 | + for (boost::filesystem::directory_iterator iter(service_dir); iter != end_iter; ++iter) | ||
83 | + { | ||
84 | + if (boost::filesystem::is_regular_file(iter->status()) && iter->path().extension() == ".json") | ||
85 | + { | ||
86 | + std::string file_name = iter->path().filename().string(); | ||
87 | + boost::cmatch what; | ||
88 | + boost::regex reg("(^\\w+)\\.(\\w+)\\.(\\w+)$", boost::regex_constants::icase); | ||
89 | + if(boost::regex_match(file_name.c_str(), what, reg) && what.size() == 4) | ||
90 | + { | ||
91 | + service_paths.push_back(iter->path().string()); | ||
92 | + } | ||
93 | + } | ||
94 | + else if (boost::filesystem::is_directory(iter->status())) | ||
95 | + { | ||
96 | + GetServicePaths(iter->path().string(), service_paths); | ||
97 | + } | ||
98 | + } | ||
99 | + return service_paths.size(); | ||
100 | +} | ||
101 | + | ||
102 | +bool DmpServerManager::LoadServices() | ||
103 | +{ | ||
104 | + //获取服务的路径 | ||
105 | + std::vector<std::string> service_paths; | ||
106 | + int size = GetServicePaths(root_path_, service_paths); | ||
107 | + if(size < 1) { | ||
108 | + return false; | ||
109 | + } | ||
110 | + for (const auto &service_path : service_paths) | ||
111 | + { | ||
112 | + //获取服务名称和服务类型vev[0],vec[1] | ||
113 | + std::string::size_type pos = service_path.find_last_of("/"); | ||
114 | + if(pos != std::string::npos) { | ||
115 | + std::vector<std::string> vec; | ||
116 | + std::string file_name = service_path.substr(pos + 1); | ||
117 | + boost::split(vec, file_name, boost::is_any_of("."), boost::token_compress_on); | ||
118 | + std::string service_name = vec[0]; | ||
119 | + | ||
120 | + if(boost::iequals(vec[1], "MapServer")) | ||
121 | + { | ||
122 | + DmpMapService* mapservice = new DmpMapService(service_name, service_path); | ||
123 | + //通知所有服务器,注册到MapServer中;为什么不由MapServer直接Add,因为MapServer不知道有多少Server,Server是动态注册到系统中的 | ||
124 | + for (const auto &server : servers_) { | ||
125 | + server.second->Register(mapservice); | ||
126 | + } | ||
127 | + if(mapservice->Read()) | ||
128 | + { | ||
129 | + services_[service_name] = mapservice; | ||
130 | + mapservice->set_state(ServiceState::STARTED); | ||
131 | + } | ||
132 | + } | ||
133 | + } | ||
134 | + } | ||
135 | +} | ||
136 | + | ||
137 | +std::shared_ptr<DmpServer> DmpServerManager::GetServer(const string &name, const string &version) | ||
138 | +{ | ||
139 | + std::shared_ptr<DmpServer> sp_server = nullptr; | ||
140 | + string key; | ||
141 | + string nameUpper = boost::to_upper_copy(name); | ||
142 | + VersionMap::const_iterator it = serverVersions_.find(nameUpper); | ||
143 | + if (it != serverVersions_.end()) | ||
144 | + { | ||
145 | + key = version.empty() ? it->second.second : MakeServerKey(nameUpper, version); | ||
146 | + ServerMap::const_iterator iter = servers_.find(key); | ||
147 | + if (iter != servers_.end()) | ||
148 | + { | ||
149 | + sp_server = iter->second; | ||
150 | + } | ||
151 | + else | ||
152 | + { | ||
153 | + sp_server = servers_[it->second.second]; | ||
154 | + } | ||
155 | + } | ||
156 | + else | ||
157 | + { | ||
158 | + } | ||
159 | + return sp_server; | ||
160 | +} | ||
161 | +void DmpServerManager::RegisterServer(std::shared_ptr<DmpServer> server) | ||
162 | +{ | ||
163 | + string name = server->Name(); | ||
164 | + boost::to_upper(name); | ||
165 | + string version = server->Version(); | ||
166 | + | ||
167 | + string key = MakeServerKey(name, version); | ||
168 | + if (servers_.find(key) != servers_.end()){ | ||
169 | + return; | ||
170 | + } | ||
171 | + servers_[key] = server; | ||
172 | + if (serverVersions_.find(name) == serverVersions_.end()){ | ||
173 | + serverVersions_.insert(pair<string, pair<string, string>>(name, pair<string, string>(version, key))); | ||
174 | + } | ||
175 | +} | ||
176 | + | ||
177 | +int DmpServerManager::UnregisterServer(const string &name, const string &version) | ||
178 | +{ | ||
179 | + int removed = 0; | ||
180 | + VersionMap::const_iterator it = serverVersions_.find(name); | ||
181 | + if (it != serverVersions_.end()) | ||
182 | + { | ||
183 | + if (version.empty()) | ||
184 | + { | ||
185 | + ServerMap::iterator iter = servers_.begin(); | ||
186 | + while (iter != servers_.end()) | ||
187 | + { | ||
188 | + if (iter->second->Name() == name) | ||
189 | + { | ||
190 | + iter = servers_.erase(iter); | ||
191 | + ++removed; | ||
192 | + } | ||
193 | + else | ||
194 | + { | ||
195 | + ++iter; | ||
196 | + } | ||
197 | + } | ||
198 | + serverVersions_.erase(it); | ||
199 | + } | ||
200 | + else | ||
201 | + { | ||
202 | + string key = MakeServerKey(name, version); | ||
203 | + ServerMap::iterator found = servers_.find(key); | ||
204 | + if (found != servers_.end()) | ||
205 | + { | ||
206 | + servers_.erase(found); | ||
207 | + removed = 1; | ||
208 | + | ||
209 | + //findGreaterVersion为匿名函数 | ||
210 | + string maxVer; | ||
211 | + std::function<void(const ServerMap::value_type &)> | ||
212 | + findGreaterVersion = [name, &maxVer](const ServerMap::value_type &item) { | ||
213 | + if (item.second->Name() == name && | ||
214 | + (maxVer.empty() || IsVersionGreater(item.second->Version(), maxVer))) | ||
215 | + maxVer = item.second->Version(); | ||
216 | + }; | ||
217 | + | ||
218 | + serverVersions_.erase(name); | ||
219 | + | ||
220 | + std::for_each(servers_.begin(), servers_.end(), findGreaterVersion); | ||
221 | + if (!maxVer.empty()) | ||
222 | + { | ||
223 | + // Set the new default service | ||
224 | + string key = MakeServerKey(name, maxVer); | ||
225 | + serverVersions_.insert(pair<string, pair<string, string>>(name, pair<string, string>(version, key))); | ||
226 | + } | ||
227 | + } | ||
228 | + } | ||
229 | + } | ||
230 | + return removed; | ||
231 | +} | ||
232 | + | ||
233 | +void DmpServerManager::CleanUp() | ||
234 | +{ | ||
235 | + serverVersions_.clear(); | ||
236 | + servers_.clear(); | ||
237 | + nativeLoader_.UnloadModules(); | ||
238 | +} | ||
239 | + | ||
240 | +std::shared_ptr<DmpServer> DmpServerManager::ServerForRequest(const DmpServerRequest &request) | ||
241 | +{ | ||
242 | + for (const auto &server : servers_) | ||
243 | + { | ||
244 | + if(server.second->Accept(request.path())) | ||
245 | + { | ||
246 | + return server.second; | ||
247 | + } | ||
248 | + } | ||
249 | + return nullptr; | ||
250 | +} | ||
251 | + | ||
252 | +bool DmpServerManager::PublishMapServer(const std::string& service_name, const std::string& capabilities, | ||
253 | + const std::string& project_data, const std::string& service_title, | ||
254 | + const std::string& catalog) | ||
255 | +{ | ||
256 | + std::string map_dir; | ||
257 | + std::string service_file; | ||
258 | + std::string proj_file; | ||
259 | + if(catalog.empty()) { | ||
260 | + map_dir = root_path_ + "/" + service_name + ".MapServer"; | ||
261 | + } else { | ||
262 | + map_dir = root_path_ + "/" + catalog + "/" + service_name + ".MapServer"; | ||
263 | + } | ||
264 | + if (!boost::filesystem::exists(map_dir)) | ||
265 | + { | ||
266 | + boost::filesystem::create_directories(map_dir); | ||
267 | + } | ||
268 | + service_file = map_dir + "/" + service_name + ".MapServer.json"; | ||
269 | + proj_file = map_dir + "/" + service_name + ".MapServer.dmd"; | ||
270 | + | ||
271 | + //project | ||
272 | + std::string proj_data; | ||
273 | + if(!DmpServerUtils::Base64Decode(project_data, &proj_data)){ | ||
274 | + return false; | ||
275 | + } | ||
276 | + DmpProject project; | ||
277 | + if(!project.Write(proj_file, proj_data)){ | ||
278 | + return false; | ||
279 | + } | ||
280 | + | ||
281 | + //services | ||
282 | + vector<string> vec_cap; | ||
283 | + boost::split(vec_cap, capabilities, boost::is_any_of(","), boost::token_compress_on); | ||
284 | + if(vec_cap.size() == 0){ | ||
285 | + return false; | ||
286 | + } | ||
287 | + DmpMapService* mapserver = new DmpMapService(service_name,service_file); | ||
288 | + for (const auto &cap : vec_cap) | ||
289 | + { | ||
290 | + std::shared_ptr<DmpServer> server = GetServer(cap, ""); | ||
291 | + if(!server->Register(mapserver)){ | ||
292 | + return false; | ||
293 | + } | ||
294 | + } | ||
295 | + | ||
296 | + if (mapserver->Write()) { | ||
297 | + services_[service_name] = mapserver; | ||
298 | + mapserver->set_state(ServiceState::STARTED); | ||
299 | + } | ||
300 | + return true; | ||
301 | +} | ||
302 | + | ||
303 | +bool DmpServerManager::PublishTileServer(const std::string& service_name,const std::string& type,const std::string& vendor, | ||
304 | + const std::string& srs,const std::string& path,const std::string& abstract,const std::string& layerinfo, | ||
305 | + const std::string& project_data,const std::string& service_title,const std::string& catalog) | ||
306 | +{ | ||
307 | + | ||
308 | + std::string map_dir; | ||
309 | + std::string service_file; | ||
310 | + std::string proj_file; | ||
311 | + if(catalog.empty()) { | ||
312 | + map_dir = root_path_ + "/" + service_name + ".MapServer"; | ||
313 | + } else { | ||
314 | + map_dir = root_path_ + "/" + catalog + "/" + service_name + ".MapServer"; | ||
315 | + } | ||
316 | + if (!boost::filesystem::exists(map_dir)) | ||
317 | + { | ||
318 | + boost::filesystem::create_directories(map_dir); | ||
319 | + } | ||
320 | + service_file = map_dir + "/" + service_name + ".MapServer.json"; | ||
321 | + proj_file = map_dir + "/" + service_name + ".MapServer.dmd"; | ||
322 | + | ||
323 | + DmpProject project; | ||
324 | + if(!project.Write(proj_file, project_data)){ | ||
325 | + return false; | ||
326 | + } | ||
327 | + | ||
328 | + //services | ||
329 | + vector<string> vec_cap; | ||
330 | + boost::split(vec_cap, type, boost::is_any_of(","), boost::token_compress_on); | ||
331 | + if(vec_cap.size() == 0){ | ||
332 | + return false; | ||
333 | + } | ||
334 | + DmpMapService* mapserver = new DmpMapService(service_name,service_file); | ||
335 | + for (const auto &cap : vec_cap) | ||
336 | + { | ||
337 | + std::shared_ptr<DmpServer> server = GetServer(cap, ""); | ||
338 | + if(!server->Register(mapserver)){ | ||
339 | + return false; | ||
340 | + } | ||
341 | + } | ||
342 | + if (mapserver->Write()) { | ||
343 | + services_[service_name] = mapserver; | ||
344 | + mapserver->set_state(ServiceState::STARTED); | ||
345 | + } | ||
346 | + | ||
347 | + return true; | ||
224 | } | 348 | } |
请
注册
或
登录
后发表评论