Schön im griff haben unsereins jedoch hervorheben, wirklich so inside sämtlichen Einzahlungen keine weiteren Transaktionsgebühren pro Zocker fällig werden. Daneben dem Willkommenspaket ferner diesem High Roller Bonus auf den füßen stehen dir inoffizieller mitarbeiter Spinrise Casino jedoch einige viel mehr Aktionen zur Order, unser wir dir nicht nicht zugestehen intendieren. 继续阅读Spin rise SpinRise Login für jedes Zocker within Deutschland angeschlossen
分类: 三个月水平
Greatest On-line casino Incentive Now offers 2025 Allege Your Free Incentives
Alternatively, the newest local casino offers you a little bit of extra fund in order to have fun with and you may earn real cash instead getting the fund at risk. We offer a call at-depth self-help guide to no deposit incentives here, and you may a whole self-help guide to our no-deposit codes which have head entry to an entertaining databases unit right here. 继续阅读Greatest On-line casino Incentive Now offers 2025 Allege Your Free Incentives
নতুন ওয়েব ভিত্তিক ক্যাসিনোগুলি দেখুন আগস্ট ২০২৫ এর সর্বশেষ সাইটগুলি
অনেক অংশগ্রহণকার ek333bangladesh.com ীদের সুপরিচিত পেমেন্ট পদ্ধতি আছে, তা সে বিটকয়েন, চার্জ, অথবা অন্য কেউ হোক। যদি আপনি নতুন ক্যাসিনো হন তবে সাধারণত নির্দিষ্ট ফি বিকল্পগুলি অফার করে, মনে রাখবেন যে আপনার পছন্দের কৌশলের উপর নির্ভর করে নির্দিষ্ট বোনাসগুলি পাওয়া যাবে না। আপনার পছন্দের শতাংশ কৌশলটি যোগ্য কিনা তা নিশ্চিত করতে অফারের ছোট মুদ্রণটি পড়ুন। শুভকামনা নতুন অনলাইন ক্যাসিনোগুলি তাদের ওয়েবসাইটগুলির জন্য চার্জ জুয়া সংস্থান সরবরাহ করে।
SlotsandCasino – নতুন নিবন্ধিত পেশাদারদের জন্য সেরা স্বাগত প্রণোদনা পরিকল্পনা
- BetWhale-এর লাইভ ব্রোকার বিকল্প রয়েছে যেমন ব্ল্যাক-জ্যাক, রুলেট এবং ব্যাকার্যাট, যা নিশ্চিত করে যে অংশগ্রহণকারীরা রিয়েল-টাইম ফর্ম্যাটে বেশ কয়েকটি জনপ্রিয় অনলাইন ক্যাসিনো গেমে অ্যাক্সেস পান।
- নির্ভরশীল প্রোগ্রামের পরিবর্তে, নতুন ওয়েব ভিত্তিক ক্যাসিনোগুলি সাধারণত কাউন্টি-অফ-দ্য-আর্টওয়ার্ক সফ্টওয়্যার সহ আসে, যা সহজ এবং নিমজ্জিত গেম খেলা নিশ্চিত করে।
- iGaming বিশ্বে ব্র্যান্ড প্রোফাইল অত্যন্ত গুরুত্বপূর্ণ, এবং নতুন ক্যাসিনো কোম্পানিগুলির কাছে নির্ভরযোগ্য এবং নির্ভরযোগ্য অপারেটরদের সাথে নিজেদের পরিচয় করিয়ে দেওয়ার সময় নেই।
- লিন্টন মিনিমাল থেকে কাজ করা এবং পরিচালনা করা, ইটারি স্থানীয় ক্যাসিনো 2016 সালে তার প্রতিষ্ঠান হিসেবে অনলাইন বেটিং নেটওয়ার্কে ঘুরে বেড়াতে পারে।
MYB জুয়া প্রতিষ্ঠানের জনপ্রিয় বৈশিষ্ট্যগুলির মধ্যে রয়েছে নিরাপদ শতাংশ বিকল্প, যার মধ্যে রয়েছে ভিসা, চার্জ কার্ড এবং দশটি সাধারণ ক্রিপ্টোকারেন্সি। এই জুয়া প্রতিষ্ঠানটি জাল কার্যকলাপ প্রতিরোধের জন্য সর্বোচ্চ স্তরের সুপারিশ সুরক্ষার নিশ্চয়তা দেয়, অনলাইনে নিরাপদ জুয়া নিশ্চিত করার জন্য 256-অংশ SSL সুরক্ষা ব্যবহার করে। আপনি কি কখনও ভেবে দেখেছেন যে অনলাইন ক্যাসিনোর ক্ষেত্র সম্পর্কে কী বলা উচিত? নতুন গেম, সুবিধা এবং সৃজনশীল বৈশিষ্ট্যগুলি বোঝার নতুন অভিযান সত্যিই উৎসাহব্যঞ্জক! কিন্তু খেলার চাহিদা পূরণের জন্য আপনি কীভাবে কোন পূর্ববর্তী ক্রমবর্ধমান দেশটি ব্রাউজ করবেন? চিন্তা করবেন না, এই বইটিতে, আমরা আপনাকে 2025 সালের সর্বশেষ অনলাইন ক্যাসিনোগুলির সাথে পরিচয় করিয়ে দেব এবং কীভাবে সেরা জুয়া বিকল্পটি পূরণ করবেন তার নির্দেশিকা প্রদর্শন করব।
মার্কিন যুক্তরাষ্ট্রে আরও ভালো ওয়েব ভিত্তিক ক্যাসিনো: বিচারক কী?
আমি পেশাদার অংশগ্রহণ উন্নত করার জন্য গেমিফিকেশনের দিকগুলিও দেখি। বিনোদনমূলক চাহিদা, লিডারবোর্ড এবং অন্যান্য গেমিফিকেশন অনলাইন ক্যাসিনো বেটিং অভিজ্ঞতাকে আরও উপভোগ্য এবং সন্তোষজনক করে তুলেছে। ধারাবাহিক র্যাঙ্কিং বিভাগগুলির মাধ্যমে, আমরা অনলাইনে সেরা নতুন ক্যাসিনোগুলি সনাক্ত করতে পারি যা উচ্চমানের অনলাইন গেম, দুর্দান্ত সুবিধা এবং খেলোয়াড়দের জন্য একটি নিরাপদ বাস্তুতন্ত্র প্রদান করে। রিয়েল-টাইম ব্রোকার গেম হল নতুন অনলাইন ক্যাসিনোর আরেকটি লক্ষ্য। এই ধরণের গেমগুলি একটি খাঁটি জুয়া খেলার পরিবেশ প্রদান করে, যা মানুষকে রিয়েল-টাইমে লাইভ খেলোয়াড় এবং অন্যান্য পেশাদারদের সাথে যোগাযোগ করতে সক্ষম করে। এই বৈশিষ্ট্যটি সম্পূর্ণ অনলাইন জুয়ার অনুভূতি বাড়ায়, তাই এটি আরও বিনোদনমূলক এবং উপভোগ্য।

সহজ নকশা জুয়ার বাস্তুতন্ত্রকে নান্দনিকভাবে আকর্ষণীয় করে তুলতে সাহায্য করে। নতুন অনলাইন ক্যাসিনোগুলির বিশ্লেষণে শব্দের ভাঙ্গন, সিস্টেম সুরক্ষা, অনলাইন গেমের ন্যায্যতা এবং আরও অনেক কিছু অন্তর্ভুক্ত। যখন Gambling establishment.org একটি নতুন অনলাইন ক্যাসিনোকে নিরাপদ হিসেবে স্ট্যাম্প করে, তখন এর অর্থ হল প্রোগ্রামটি একটি উচ্চমানের পাব সরিয়ে দেয় — যা খেলোয়াড়দের যথেষ্ট সময়-পরিচয় রক্ষা করে। নতুন ক্যাসিনো এবং প্রচারগুলিতে অংশগ্রহণের জন্য আগ্রহী খেলোয়াড়দের জন্য, সর্বশেষ অনলাইন ক্যাসিনোগুলি সম্পর্কে জানা গুরুত্বপূর্ণ।
ইন্টারনেটে নতুন ক্যাসিনো 2024 মার্কিন যুক্তরাষ্ট্র
৬০০ টিরও বেশি ভিডিও গেম, ৫০০ টিরও বেশি পোর্ট, প্রায় ১০০ টেবিল গেম এবং সেই সমস্ত রিয়েল-টাইম এজেন্ট গেম সহ, ডোমিন্যান্স লঞ্চার ক্যাসিনো সকলের জন্য কিছু না কিছু অফার করে। অ্যালাইভ ডিলার অনলাইন গেম হল ব্যালি-প্রাইভেট শিরোনাম এবং ব্ল্যাক-জ্যাক এবং রুলেট সহ ক্লাসিক। ২০২৫ সালে সেরা অনলাইন ক্যাসিনো অভিজ্ঞতা পেতে, আরও উল্লেখ করা শীর্ষ ১০টি সেরা অনলাইন ক্যাসিনো দেখুন। ওয়েবসাইটগুলি একটি ভাল জুয়ার অভিজ্ঞতা নিশ্চিত করার জন্য অসংখ্য গেম এবং প্রচুর বোনাস অফার করে। অনলাইন ক্যাসিনোগুলি দায়িত্বশীল গেমিং পণ্য প্রদান করে যেমন মোড ব্যক্তিগত জুয়ার সীমাবদ্ধতা এবং তাদের স্থায়ী জুয়ার অভ্যাসের জন্য টিপস অ্যাক্সেস। TLS এবং হাইব্রিড RSA সহ নিরাপত্তা প্রযুক্তি নিরাপদ অনলাইন ক্যাসিনো দ্বারা ব্যবহার করা যেতে পারে।
নিবন্ধিত প্ল্যাটফর্মগুলি কীভাবে নিরাপদ খেলা নিশ্চিত করে
আমাদের সেরা নির্বাচনগুলি তাৎক্ষণিক অবস্থান এবং দ্রুত নগদ অর্থ প্রদান করে যার মধ্যে নমনীয় সীমাবদ্ধতা (কম সর্বনিম্ন, উচ্চ সর্বোচ্চ) এবং বিভিন্ন ধরণের শতাংশ পদ্ধতি রয়েছে — ক্রিপ্টো সমন্বিত। আপনি যদি সত্যিকার অর্থে আয়ের অনলাইন ক্যাসিনো গেম খুঁজছেন, অনলাইন ক্যাসিনো, অথবা আপনার কাউন্টিতে কোন জুয়া খেলার আদালত রয়েছে তা পরীক্ষা করছেন — এই পৃষ্ঠাটি সেরা বিকল্প। আমি 2025 সালে খেলোয়াড়দের জন্য ইট-পাথরের দোকান থেকে শুরু করে সেরা জুয়া খেলার সাইট পর্যন্ত সবকিছুই সুরক্ষিত রাখি।
- আত্মবিশ্বাসী হোন, আমি যে সমস্ত ওয়েবসাইটগুলিকে সুপারিশ করছি সেগুলি সম্পূর্ণরূপে অনুমোদিত এবং নিরাপদ, যত্ন নেওয়ার পরিবর্তে অনুভূতি দেখার উপর মনোনিবেশ করা।
- এই পৃষ্ঠায়, আমরা বড় ব্র্যান্ড - নতুন জুয়া সংস্থাগুলি নিয়ে আলোচনা করেছি যা আপনাকে অবশ্যই নতুন জুয়ার অভিজ্ঞতা নিশ্চিত করবে।
- এই ধারণার কারণে, স্লটস্পট অনলাইন ক্যাসিনো সুরক্ষিত করার চেষ্টা করা যে কোনও ব্যক্তির জন্য একটি স্পিন-টু সাইট হয়ে ওঠে, যার উপর নির্ভর করে কেবল বিশ্বাসযোগ্যতাই নয়।
- এই ধরণের পদ্ধতি অনুসরণ করে, আপনি নতুন অনলাইন ক্যাসিনোগুলির কারণে বিবেচনা করা বড় প্রণোদনা এবং অফারগুলি থেকে উপকৃত হতে পারেন এবং আপনার বাজির অনুভূতিটি অনুকূল করতে পারেন।
নতুন অনলাইন ক্যাসিনো ইউএস-এর জন্য নিয়ন্ত্রণে থাকা বাজির টিপস একটি গুরুত্বপূর্ণ বিষয়, যা ব্যবহারকারীর আগ্রহের সাথে অগ্রগতির ভারসাম্য বজায় রাখার প্রতি তাদের নিষ্ঠা প্রদর্শন করে। এই অগ্রগতিগুলি কেবল খেলোয়াড়দের অনুভূতি উন্নত করে না এবং অনলাইন ক্যাসিনোগুলিকে বৈচিত্র্যময়, প্রযুক্তি-স্মার্ট দর্শক আকর্ষণ করার জন্য পরিবেশন করে। ব্ল্যাক লোটাসের মতো প্ল্যাটফর্মগুলি মসৃণ, আকর্ষণীয় এবং প্রগতিশীল গেমিং পরিবেশ প্রদানের জন্য এই ধরণের প্রযুক্তি ব্যবহার করে। বেশিরভাগ প্রক্রিয়ার জন্য কমপক্ষে $20 (বেশিরভাগ ক্রিপ্টো বিকল্পের জন্য $10) থেকে শুরু হয়, যার তাৎক্ষণিক চার্জ ছাড়াই পরিচালনা করা যায়। অপারেটিং প্ল্যাটফর্মটি USD, CAD, EUR, INR, NOK এবং DKK সহ বিভিন্ন মুদ্রাকে সমর্থন করে, যা সীমাবদ্ধতার বাইরে পেশাদারদের থাকার স্বাধীনতা নিশ্চিত করে। সমস্ত লেনদেন 128-অংশ SSL এনক্রিপশন দ্বারা আচ্ছাদিত, সুরক্ষার জন্য নেটওয়ার্ক মান মেনে চলে।

এছাড়াও, নতুন অনলাইন ক্যাসিনোগুলি ব্যবহারকারীদের পছন্দের উপর ভিত্তি করে তৈরি করা হয়। তারা গেম বুক করে এবং আরও পরিপক্ক ক্যাসিনো সংস্থাগুলিকে আপনার সমস্ত কিছু ব্যবহার করতে দেয়, যাতে আপনার খেলার অনুভূতি তাজা এবং মজাদার হয়। একটি ভিন্ন ক্যাসিনো ওয়েবসাইট বেছে নেওয়ার ফলে গেমটিতে অ্যাক্সেস এবং আপনার উপায় এবং পছন্দ অনুসারে একটি প্ল্যাটফর্ম থাকে। সামগ্রিকভাবে, অনলাইন ক্যাসিনোগুলি ঐতিহ্যবাহী ক্যাসিনো ছাড়াই একটি নতুন এবং মজাদার ক্যাসিনো অভিজ্ঞতা প্রদান করে। নতুন গেম, প্রযুক্তি, বৈশিষ্ট্য যোগ করার মাধ্যমে, এই ক্যাসিনোগুলি ব্যবহারকারীদের আরও মজাদার এবং সন্তোষজনক অনুভূতি প্রদান করতে সক্ষম। এছাড়াও, অনলাইন ক্যাসিনোগুলি নতুন পেশাদারদের আকর্ষণ করার জন্য আরও উদার গ্রহণযোগ্যতা প্রণোদনা এবং প্রচার প্রদান করে।
Porsche: Precision Crafted Performance
Porsche is known for its perfect balance between luxury and sportiness. The brand focuses on precision engineering and driving control.
Porsche vehicles are praised for exceptional handling and responsiveness. They perform equally well on everyday roads and race tracks.
This combination makes Porsche a symbol of refined performance and technical excellence.
XML-RPC Test Post
Automated test via XML-RPC.
Pin Up Casino – Официальный сайт казино Пин Ап: способы входа через альтернативные зеркала (2025)
В сфере онлайн-гемблинга, где постоянно возникают новые площадки, подбор действительно защищенного и стабильного клуба может представлять сложность. Pin Up Casino — один из таких удачных вариантов. Это обладающий лицензией оператор, предоставляющий своим пользователям широкую коллекцию азартных продуктов: автоматы, карточные игры, рулетку и многое другое.
Pin Up Casino — международная игровая платформа, созданная в 2016 году. За время существования она смогла заработать доверие игроков по всему миру за счет продвинутых систем защиты, гарантирующих сохранность личных данных.
Официальный ресурс казино дает гостям полный набор функций: авторизацию в учетной записи, получение поощрений и участие в турнирах. В распоряжении пользователей — свыше 3000 развлечений, включая слоты, игры с живыми дилерами и рулетку. Дополнительные эмоции обеспечивают акционные предложения и состязания.
Pin Up Casino также славится высоким уровнем безопасности. Площадка применяет современные алгоритмы шифрования для предотвращения недобросовестных действий. Наличие международной лицензии доказывает её надежность и устойчивость.
Если вы в поисках проверенного онлайн-казино для игры на реальные деньги с возможностью вывода выигрышей — Pin Up Casino окажется прекрасным выбором. Официальный сайт обеспечивает все условия для захватывающего и удобного отдыха.
Внимание! Для перехода на зеркало Pin Up Casino применяйте исключительно проверенные источники, чтобы не оказаться жертвой злоумышленников. Не переходите по ссылкам на сомнительные сайты.
Pin Up Casino – ваш надежный партнер на пути к успеху!
Pin Up Casino – Официальный портал Пин Ап.
Pin Up Casino – это известная игровая площадка, предлагающая пользователям многообразные развлекательные и финансовые опции. Официальный сайт служит центральным узлом для безопасного и комфортного досуга.
На платформе доступен широкий выбор игр: автоматы, настольные игры, рулетка, бинго и прочие. Все они разработаны с использованием современных технологий, что обеспечивает качественную графику и стабильную работу.
Помимо этого, казино поощряет активность пользователей бонусами и наградами за привлечение друзей. Это повышает шансы на победу и делает игровой процесс ещё увлекательнее.
Таким образом, официальный сайт Pin Up Casino предоставляет всё нужное для качественного отдыха и игры.
Доступ через альтернативные адреса (2025)
В условиях серьезной конкуренции в области азартных развлечений Pin Up Casino удалось выделиться и занять лидирующие места. Этому поспособствовало не только разнообразие контента, но и разные способы доступа к платформе.
Один из таких способов – вход через зеркало сайта. Зеркало является альтернативным адресом, дающим возможность получить доступ к казино при блокировке или неработоспособности главного сайта. Это особенно значимо для игроков, которые сталкиваются с ограничениями.
Вход через зеркало Pin Up Casino – это легкий и оперативный способ подключиться к играм. Достаточно ввести адрес зеркала в браузере и авторизоваться привычным методом – через социальные сети или логин с паролем.
Через зеркало предоставляется полный доступ ко всем играм казино: слотам, рулетке, бинго и другим. Это дает возможность играть когда угодно и где угодно при наличии интернета.
В целом, вход через зеркало Pin Up Casino – это удобный и надежный способ обхода блокировок для продолжения игры.
Сильные стороны и функционал Pin Up Casino.
Pin Up Casino – это известная игровая площадка, предлагающая пользователям обширный каталог автоматов, азартных игр и развлекательных программ. Вот главные достоинства и функции, которые позволяют ему быть одним из лучших в России.
Уникальные возможности.
Большой выбор игровых автоматов и азартных игр Высокие ставки и выплаты Мобильная версия сайта для пользователей смартфонов и планшетов Многоязычный интерфейс Квалифицированная служба поддержки.
Преимущества.
Pin Up Casino – это место, где вы можете насладиться игрой и получать выигрыши. Сайт предлагает широкий набор функций и преимуществ, которые делают его одним из лучших онлайн-казино в России.
Как начать играть в Pin Up Casino.
Чтобы начать играть в Pin Up Casino, необходимо зарегистрироваться на официальном сайте pinup.casino. Процесс создания учётной записи отнимет всего несколько минут.
Нужно выбрать тип аккаунта: игрок или дилер. Для игры выбирайте опцию «игрок».
Шаг 1: регистрация.
Для регистрации потребуется указать личные данные: имя, фамилию, дату рождения, e-mail и номер мобильного телефона. Также необходимо придумать пароль.
После заполнения формы на электронную почту придет письмо с подтверждением. Нужно перейти по ссылке для активации аккаунта.
Шаг 2: пополнение счета.
После регистрации требуется внести депозит. Доступные методы пополнения: кредитная карта, электронный кошелек, банковский перевод.
Выберите подходящий способ, укажите сумму пополнения и предпочтительную валюту.
После пополнения баланса откроется доступ ко всем игровым автоматам и столам.
Начните играть, выбрав понравившийся слот или игру. Доступны слоты, рулетка, бинго и другие развлечения.
Также игрокам доступны различные бонусы и акции, которые помогут начать игру и повысить шансы на выигрыш.
Наслаждайтесь игрой в Pin Up Casino!
Elasticsearch Query DsL查询
一 Elasticsearch简介
Elasticsearch 是一个开源的搜索引擎,Elasticsearch 使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单, 通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。
- 一个分布式的实时文档存储,每个字段 可以被索引与搜索
- 一个分布式实时分析搜索引擎
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
二 安装并运行
已经在其他文档中详细介绍,此次仅做简单步骤介绍
# 安装:
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.13.3-linux-x86_64.tar.gz
$ tar -xzf elasticsearch-7.13.3-linux-x86_64.tar.gz
$ cd elasticsearch-7.13.3/
# 运行
sh bin/elasticsearch
# 访问
$ curl http://192.168.3.14:9200/
{
"name" : "87DNZWU",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "e3A3l85MSZuZlRhxj6IB2w",
"version" : {
"number" : "6.7.0",
"build_flavor" : "default",
"build_type" : "zip",
"build_hash" : "8453f77",
"build_date" : "2019-03-21T15:32:29.844721Z",
"build_snapshot" : false,
"lucene_version" : "7.7.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
三 Query DSL 基本结构
查询表达式(Query DSL)是一种非常灵活又富有表现力的查询语言, Elasticsearch 使用它可以以简单的 JSON 接口来展现 Lucene 功能的绝大部分
// 查询
GET /_search // 查找整个ES中所有索引的内容
{
"query": {}, //具体的查询语句对象
"from": 0, //从第几条数据开始返回
"size": 100, //返回的条数 默认ES最多返回10000条
"highlight": { //高亮
"pre_tags": {}, //高亮内容的前面标签 一般都是html比如<b> <p>这种
"post_tags": {},//高亮内容的后面标签 一般都是html比如</b> </p>这种
"fields": { //需要高亮的字段
}
},
"sort": [{ //排序
"FIELD": { //排序的字段(需要填上具体的字段名)
"order": "desc"
}
}],
"_source": "{field}" //指定返回的字段
}
// 结果
{
"took": 350, // 整个搜索请求消耗了多少毫秒
"timed_out": false, // 表示本次查询是否超时,如果为true也会返回结果,只是数据可能不完整
"_shards": { // 显示查询中参与的分片信息,成功多少分片失败多少分片等
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 5, // 匹配到的文档总数
"max_score": 1, // 为文档中所有_score的最大值
"hits": [
{
"_index": "mysql-shop_trades-order_item_label_binds",
"_type": "doc",
"_id": "591935",
"_score": 1,
"_source": {
"id": 591935,
"updated_at": "2021-05-20T06:26:09.000Z",
"@version": "1",
"bind_item_label_id": 729,
"label_type": "brand",
"created_at": "2021-05-20T06:26:09.000Z",
"@timestamp": "2021-07-07T07:31:36.262Z",
"is_deleted": 0,
"table_name": "order_item_label_binds",
"bar_code": "6907925004486"
}
}
]
}
}
四 指定索引搜索
上述查询会搜索ES中的所有索引,但通常只需要去固定一个或几个索引中搜索,搜索全部无疑会造成资源的浪费,在ES中可以通过以下几种方法来指定索引
- 指定一个固定的索引,
ops-coffee-nginx-2019.05.15为索引名字
GET /mysql-shop_trades-order_statics/_search
以上表示在mysql-shop_trades-order_statics索引下查找数据
- 指定多个固定索引,多个索引名字用逗号分割
GET /mysql-shop_trades-order_statics,mysql-shop_trades-order_item_labels/_search
- 用*号匹配,在匹配到的所有索引下查找数据
GET /mysql-shop_trades-*/_search
这里也可以用逗号分割多个匹配索引
五 DSL查询
1、筛选字段
// 筛选_source的数据,单个字段
GET /_search
{
"_source": "bar_code",
"query": {}
}
// 筛选_source的数据,多个字段
{
"_source": {
"includes": ["store_id", "sku_id"]
},
"query": {}
}
// 对字段进行转换
{
"docvalue_fields": [
{
"field": "updated_at",
"format": "yyyy-MM-dd HH:mm:ss"
},
{
"field": "num",
"format": "long" // 没有作用,懵逼...
}
],
"query": {}
}
2、多条件查询 (where)
- constant_score:装另一个查询的查询,固定分数查询,支持filter查询,不支持match查询:
{ "constant_score": { "filter": { "match": { "name": "小米" } }, "boost": 10 } } - bool:主要与其他关键字组合使用,多条件的查询必须要用bool包在外层,然后再根据具体的业务来拼接。
{
"query": {
"bool": {
"should": [{}], //满足其中一个对象查询条件就行 像sql里的or
"must": [{}], //必须满足所有对象的查询条件 就像sql里的and
"must_not": [{}] //必须不满足所有对象的查询条件 就像sql里的and !=
}
}
}
- must: 类似于SQL中的AND,必须包含
- must_not: 类似于SQL中的NOT,必须不包含
- should: 满足这些条件中的任何条件都会增加评分
_score,不满足也不影响,should只会影响查询结果的_score值,并不会影响结果的内容 - filter: 与must相似,但不会对结果进行相关性评分
_score,大多数情况下我们对于日志的需求都无相关性的要求,所以建议查询的过程中多用filter
3、group by:
ES本身没有group关键词搜索,但支持聚合查询,,需要使用关键字aggs
// 单个字段 group by
{
"query":{},//这里省略你的查询条件
"aggs": {
"age_group": {//这个是指你要返回字段名
"terms": { //这里还可以用其它关键词 这里terms才能实现group by效果
"field": "age",//groupby的字段
"size":1 //返回的条数 相当于group by limit
}
}
}
}
// 多字段group by (如 group by sku_id,store_id)
// 方法一:script
{
"query":{},
"aggs": {
"age_group": {
"terms": {
"script":{
"source": """ 's' + doc['store_id'] + '_s' + doc['sku_id'] """,
"lang": "painless"
},
"size": 10
}
}
}
}
// 方法二:copy to
1. 设置mapping中的多个字段,copy_to 为同一个字段(skuId_storeId)
2. 搜索新字段
{
"query":{},
"aggs": {
"list": {
"terms": {
"field": "skuId_storeId
"size":1
}
}
}
}
// 方法三:multi_terms (使用高版本,目前6.7不支持)
{
"aggs": {
"genres_and_products": {
"multi_terms": {
"terms": [{
"field": "genre"
}, {
"field": "product"
}]
}
}
}
}
4、order by
order by:注意日期格式和数值格式才支持排序;文本不支持,如果要排序, 需把字段设置为not analysis
// 单排序
{
"query": {
"sort": {
"id": "desc"
}
}
}
// avg按照平均值排序
{
"query": {
"sort": [
{
"id": "desc"
},
{
"price": {
"order": "asc",
"mode": "avg"
}
}
]
}
}
5、count(distinct)
{
"query":{},
"aggs": {
"total_sku_id": {
"cardinality":{ "field": "sku_id"}
},
"total_entity_store_id": { // 非数字类型,无法使用field排序,可以对field增加fieldData = true,或者对field.keyword排序,建议使用后者,高效内存消耗低
"cardinality":{ "field": "entity_store_id.keyword"}
}
}
}
6、SUM
{
"query":{},
"aggs": {
"total_pay_num": {
"sum": {"field": "num"}
},
"total_cost_fee": {
"sum": {"field": "cost_fee"}
}
}
}
7、distinct :
select distinct(id) from table
{
"query":{},
"collapse": {
"field": "id" //你需要distinct的字段
},
}
8、limit
1. 分页:
1. form:从第几个开始查询,最开始是0
2. size:即limit
3. 使用size,size最大可获取数量是xx个
2. 获取所有数据的三种方式
1. scroll深度滚动需要根据scroll_id和循环取,取完后,需删除scroll,减小内存开销(深度滚动高效,用于处理大量数据,不适合实时获取)
2.调整索引index.max_result_window的大小,默认10000 (大小与堆内存成正比,这个限制内存)
3.search_after:请求需增加前一个结果的排序,(实时游标,可根据索引更新和删除而改变 )
4。 如果是group by查询获取所有数据, 获取需要使用到cardinality查询预估总数,再使用partition、num_partitions分区依次获取数据
9、搜索关键字
- match:自定字段,根据字段关键字进行搜索,会分割关键词,匹配到含有一个多多个词的匹配
- query_string:全文搜索
- match_phrase:不分割关键词 {"match_phrase": {"name":"婴幼儿奶粉"}}
- term: 类似SQL where field = x,主要用于数字匹配;如果要匹配文本,会自动分词,不能精准查询,需把字段设置成not analysis
{ "query": { "term": {"bind_item_label_id": 729} } } - terms: 类似SQL where field in (x,x),主要用于数字匹配,
{ "query": { "terms": {"bind_item_label_id": [703,729]} } } - range:: 查询价格在1000-2000的商品
{
"query": {
"range": {
"price": {
"gte": 1000,
"lte": 2000
}
}
}
}
- filter:判断文档是否满足条件
{
"query": {
"bool": {
"filter": {
"term": {
"price": 1999
}
}
}
}
}
Elasticsearch:Aggregation
- metric:度量聚合,主要针对number类型的数据,需要es做较多的计算工作(类似SQL的SUM、MAX、AVG、MIN、Cardinality、stats<属于多值分析>等)
- bucket:桶聚合,划分不同步的桶,将数据分配到不同的桶,(类似SQL中的group by)
- Pipeline Aggregation:管道分析类型,对其他聚合结果进行二次聚合
- Matrix Aggregation:矩阵分析类型,支持对多个字段的操作并提供一个结果矩阵
term aggregation
- size 可以通过size返回top size的文档,该术语聚合针对顶层术语(不包含嵌套词根),其搜索过程是将请求向所有分点发送请求,每个分片节点返回size条数据,然后聚合所有分片的结果(会对各分片返回的同样词根的数数值进行相加),最终从中挑选size条记录返回给客户端。从这个过程也可以看出,其结果并不是准确的,而是一个近似值。
- Shard Size 为了提高该聚合的精确度,可以通过shard_size参数设置协调节点向各个分片请求的词根个数,然后在协调节点进行聚合,最后只返回size个词根给到客户端,shard_size >= size,如果shard_size设置小于size,ES会自动将其设置为size,默认情况下shard_size建议设置为(1.5 * size + 10)。
// 单个字段 group by
{
"query":{},//这里省略你的查询条件
"aggs": {
"age_group": {//这个是指你要返回字段名
"terms": { //这里还可以用其它关键词 这里terms才能实现group by效果
"field": "age",//groupby的字段
"size":1 //返回的条数 相当于group by limit
}
}
}
}
// 返回结果格式
{
...
"aggregations" : {
"list" : {
"doc_count_error_upper_bound" : 0, // 该值表示未进入最终术语列表的术语的最大潜在文档计数
"sum_other_doc_count" : 90 // 该值表示未进入最终术语列表的术语的最大潜在文档计数
"buckets" : [ // 返回doc_count排名最前的10个,受size参数的影响
{
"key" : "1",
"doc_count" : 24,
"total_refund_fee" : {
"value" : 0.0
},
"total_cost_fee" : {
"value" : -14976.0
},
}
]
}
}
}
}
match_phrase :查询分析文本,创建词组查询
举例子
GET mysql-shop_trades-order_item_label_binds/_search/?scroll=1m
{
"docvalue_fields": [
{
"field": "updated_at",
"format": "yyyy-MM-dd HH:mm:ss"
}
],
"size": 1000,
"sort": {"id":"desc"},
"query": {
"bool": {
"must": [
{"match": {"is_deleted": 0}},
{"match": {"label_type": "brand"}},
{
"constant_score": {
"filter": {
"terms": {
"bind_item_label_id": [703, 2, 729]
}
}
}
}
]
}
}
}
GET mysql-shop_trades-order_item_label_binds/_search/?scroll=1m
{
"_source": "bar_code",
"query": {
"bool": {
"filter": [
{"match": {"is_deleted": 0}},
{"match_phrase": {"label_type": "brand"}},
{"terms": {"bind_item_label_id": [703, 2, 729]}}
]
}
},
"aggs": {
"bar_code_group": {
"terms": {
"field": "bar_code.keyword",
"size": 10
}
}
}
}
GET mysql-shop_trades-order_item_label_binds,mysql-shop_trades-order_statics/_search
{
"query": {
"bool": {
"filter": [
{"match_phrase": {"sys_name": "yiqigou"}},
{"range": {"num": {"lte": 2000}}},
{"range": {"return_num": {"gte": -1000}}},
{"range": {"total_price": {"lte": 1000000}}},
{"match": {"id": 60}},
{"term": {"order_type": 0}},
{"term": {"item_type": 0}},
{"range": {"date": {
"gte": "2020-01-21 00:00:00",
"lte": "2021-07-22 00:00:00",
"format": "yyyy-MM-dd HH:mm:ss",
"time_zone": "+08:00"
}}}
],
"must_not": [
{"terms": {"store_id": [165]}}
]
}
}
}
设置fieldData
// 第一步,创建索引 (如果已经有索引,直接看第二步)
PUT mysql-shop_trades-order_statics2
{
"mappings": {
"_doc": {
"properties": {
"entity_store_id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}
}
}
// 第二步 设置fieldData为true
PUT mysql-shop_trades-order_statics/_mapping/_doc
{
"properties": {
"entity_store_id": {
"type": "text",
"fielddata": true
}
}
}
// 第三步 可以查看该索引的Mapping结构,fieldData是否加上去
{
"mapping": {
"doc": {
"properties": {
"entity_store_id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"fielddata": true
}
}
}
}
}
延伸
设置 max_result_window
PUT /mysql-shop_trades-order_statics/_settings
{
"index": {
"max_result_window": 100000
}
}
Spring Boot 入门
一、开发基础
- Java基础(两到三小时过一遍)
- Java开发环境配置(必须使用JDK1.8)
- IDE安装(优先使用IntelliJ IDEA)
二、名词解释
- Spring:JAVA开发应用框架
- Spring Boot:用来简化Spring应用的初始搭建以及开发过程的配置框架
- Maven:Java项目构建工具,成熟的项目
- Gradle:更简洁的Java项目构建工具,吸收了旧构建工具的优点。
- JPA:是Sun官方提出的Java持久化规范,即数据库操作规范。
- Hibernate:Hibernate是一个ORM框架,是JPA的默认实现方式,一般说JPA都是指Hibernate。
- Mybatis:Mybatis是一个轻便的ORM框架。
- Spring data jpa:是Spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架
三、新项目流程
- 新建gradle项目
- File->new->Prroject->Spring Initializr
-
填写Group、Artifact选择Gradle Project项目生成
-
可以直接在生成项目的时候选择对应需要安装的插件,如:web、jpa、mybatis等,也可以在项目初始化完成之后在build.gradle中添加/配置
-
配置build.gradle(位于根目录)
plugins { id 'org.springframework.boot' version '2.1.3.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.duomai' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' // JDK最大兼容版本 repositories { mavenCentral() } dependencies { // Spring Boot JPA 组件 implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // Spring Boot Web组件 implementation 'org.springframework.boot:spring-boot-starter-web' // Mybatis插件,注意暂时使用**1.1.1**版本,高版本的运行好像有问题 implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.1.1' // Mybatis分页插件 implementation group: 'com.github.pagehelper', name: 'pagehelper-spring-boot-starter', version: '1.2.10' runtimeOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'mysql:mysql-connector-java' testImplementation 'org.springframework.boot:spring-boot-starter-test' }修改了build.gradle后,idea会自动安装/更新依赖包。
参考:gradle官网、Spring Boot Web服务搭建、Spring Boot Mysql使用、Spring Boot JPA使用 -
项目基础配置(位于
src/resources/application.properties)#运行配置 server.port=9000 #数据格式配置 spring.jackson.time-zone=GMT+8 // 设置接口返回时区为东八区 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss // 自动将接口返回中的日期格式转换为标准格式 #数据库连接配置 spring.datasource.url=jdbc:mysql://192.168.0.235:3355/shop_balances?serverTimezone=Asia/Shanghai&tinyInt1isBit=false // serverTimezone选择Mysql东八区,tinyInt1isBit禁止Mysql自动将tinyint(1)类型数据映射为boolean类型 spring.datasource.username=cishop spring.datasource.password=fuyuan1906 #log配置 logging.path=E:/java/demo/balance_card/log logging.level.com.favorites=DEBUG #logging.level.org.springframework.web=INFO logging.level.org.hibernate=ERROR #mybatis设置 mybatis.type-aliases-package=com.duomai.balance_card.Model.Mapper mybatis.configuration.map-underscore-to-camel-case=true logging.level.com.duomai.balance_card.Model.Mapper=DEBUG #pagehelper插件设置 pagehelper.helperDialect=mysql pagehelper.reasonable=false pagehelper.supportMethodsArguments=true pagehelper.params=pageNum=page;pageSize=limit #jpa 设置 spring.jpa.properties.hibernate.hbm2ddl.auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.show-sql= true - 项目启动
- IDEA启动,运行[Prefix]Application.java文件即可
-
命令行启动
- Maven项目
在springboot的应用的根目录下运行mvn spring-boot:run -
Grdale项目
在springboot的应用的根目录下运行gradle bootRun或gradlew bootRun
(前者是使用本地的gradle版本运行,后者是使用代码仓库中的gradle运行)
- Maven项目
-
打包构建可执行文件运行
-
开发环境热更新设置
热更新不是很好用,有一定的延迟时间
四、项目文件分层解析
// 业务代码 src/main
java
com
duomai
balance_card
[Prefix]Application.java // 项目启动文件,可以做一些全局设置,如时区设置、Mapper扫描等
Config // 配置类,用于注册一些全局配置,如拦截器注册等
Middleware // 中间件,实现AOP功能
Controller // 控制器,主要做路由功能
xxxController.java
BaseErrorController.java // 路由匹配失败时使用的控制器
Service // 业务代码
Model // 目录主要用于实体与数据访问层
Entity // 数据表实体类
Repository // JPA数据仓库
Mapper // Mybatis映射文件
Provider // Mapper的Sql生成器
Library // 库类,存放公共类文件/纯定义文件等
ApiReturnDefines.java // 接口返回定义
ExceptionErrprDefines.java // 异常监听层定义
Helper // 辅助函数类文件
OutPut // 接口输出层
ApiResult.java // 最终的接口输出格式
AiReturn.java // 快速生成ApiResult类,供外部调用
Exception // 统一的异常处理
ControllerHandler // 路由层异常监听
SqlHandler // 数据库层异常监听
// 配置项
resources
appliaction.properties // 项目配置文件
五、控制器中间件
-
一般使用Spring过滤器或拦截器实现AOP切面编程
-
过滤器和拦截器的对比
- 作用域不同
过滤器依赖于servlet容器,只能在 servlet容器,web环境下使用。
拦截器依赖于spring容器,可以在spring容器中调用,不管此时Spring处于什么环境。 - 细粒度的不同
过滤器的控制比较粗,只能在请求进来时进行处理,对请求和响应进行包装。
拦截器提供更精细的控制,可以分为controller对请求处理之前、渲染视图之后、请求处理之后三个切面。 - 中断链执行的难易程度不同
拦截器可以 preHandle方法内返回 false 进行中断。
过滤器就比较复杂,需要处理请求和响应对象来引发中断,需要额外的动作,比如将用户重定向到错误页面。
- 作用域不同
- 拦截器的使用
- 编写自定义拦截器(
Middleware/ControllerInterceptor.java)
public class ControllerInterceptor implements HandlerInterceptor { private Logger logger = LoggerFactory.getLogger(ControllerInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("preHandle...."); // token校验等 String token = request.getHeader("token"); // Common.sendJson(response, ApiReturn.fail(1001, "token验证失败")); // return false; return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { logger.info("postHandle..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 接口日志记录等 logger.info("afterCompletion..."); } ``` **说明**: preHandle:对客户端发过来的请求进行前置处理,如果方法返回true,继续执行后续操作,如果返回false,执行中断请求处理,请求不会发送到Controller。可以在这里校验一些权限信息,如token等,校验失败直接以JSON格式返回请求。 postHandler:在请求进行处理后执行,也就是在Controller方法调用之后处理,前提是preHandle方法返回true。具体来说,postHandler方法会在DispatcherServlet进行视图返回渲染前被调用。 afterCompletion: 该方法在整个请求结束之后执行,前提依然是preHandle方法的返回值为true。- 注册拦截器(
Config/InterceptorConfig.java)
@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override // 核心方法 public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(ControllerInterceptor()) //配置拦截规则 .addPathPatterns("/**") .order(1); // 多个拦截器按上述方法持续注册即可,同时也可以设置order值,从小到大执行。 } @Bean public HandlerInterceptor ControllerInterceptor() { return new ControllerInterceptor(); } } - 编写自定义拦截器(
六、Controller层
- 定义控制器文件
@RestController 在类文件头部定义,标明为控制器文件,且输出格式为JSON - 路由和参数
- 定义路由名称,接收方法
例:@RequestMapping(value = "/get", method = {RequestMethod.GET, RequestMethod.POST}) 可选参数: value:路由名称 method:指定请求的method类型, GET、POST、PUT、DELETE、PATCH等,可多选 consumes:指定处理请求的提交内容类型(Content-Type),例如application/json, text/html produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回 params: 指定request中必须包含某些参数值 headers: 指定request中必须包含某些指定的header值 - 参数接收
例:@RequestParam(value = "fields", required = false, defaultValue = "*") String fields value:参数名称 defaultValue:默认值 required:是否是必要参数 - 自定义错误路由
- 在Controller层中添加BaseErrorController.java文件,用于监听路由匹配失败的情况
@Controller public class BaseErrorController implements ErrorController { @Override public String getErrorPath() { System.out.print("错误页面"); return "error/error"; } @RequestMapping(value = "/error") public void error() throws Exception { throw new Exception("路由匹配失败"); } } - 在Exception文件夹中添加ControllerHandler.java,用于捕获路由报错并输出。
@RestControllerAdvice public class ControllerHandler { // 缺少必选参数 @ExceptionHandler({MissingServletRequestParameterException.class}) @ResponseBody public ApiResult requestMissingServletRequest(MissingServletRequestParameterException e){ return ApiReturn.fail(ExceptionErrorDefines.RequestMissingServletRequest, e.getMessage()); } } 未解决:抛出异常后访问404页面运行环境会报错,但是页面正常参考:https://www.jianshu.com/p/393f70b55b1b
- 在Controller层中添加BaseErrorController.java文件,用于监听路由匹配失败的情况
- 定义路由名称,接收方法
-
Service层调用
- Service类成员注入
- 使用@Autowired修饰符进行依赖注入
@Autowired private final CardService cardService; - 用构造函数来做注入类成员(推荐使用)
private StoreBalanceCardsRepository cardsRepository; public CardController(StoreBalanceCardsRepository cardsRepository) { this.cardsRepository = cardsRepository; } **注**: IntelliJ IDEA使用依赖注入会有IDE报错,但不影响实际编译运行,如需去除报错提示,需要在Dao层(Respository/Mapper)类开头添加注解 `@Repository`
- 使用@Autowired修饰符进行依赖注入
- 调用
cardService.get(id, fields);
- Service类成员注入
七、Service层
- 定义Service文件
@Service 在类文件头部定义,标明为Service文件 - 注入Model层操作文件
private final StoreBalanceCardsRepository storeBalanceCardsRepository; private final StoreBalanceCardsMapper storeBalanceCardsMapper; CardService(StoreBalanceCardsRepository storeBalanceCardsRepository, StoreBalanceCardsMapper storeBalanceCardsMapper) { this.storeBalanceCardsRepository = storeBalanceCardsRepository; this.storeBalanceCardsMapper = storeBalanceCardsMapper; } - 调用Model层文件
八、Model层
-
Repository
- Spring中概念,概念类似于数据仓库,是Spring data jpa的实现。居于业务层和数据层之间,将两者隔离开来,在它的内部封装了数据查询和存储的逻辑。
-
Repository和传统意义上的DAO的区别:
Repository蕴含着真正的OO概念,即一个数据仓库角色,负责所有对象的持久化管理。DAO则没有摆脱数据的影子,仍然停留在数据操作的层面上。Repository是相对对象而言,DAO则是相对数据库而言,虽然可能是同一个东西,但侧重点完全不同。
-
Mapper
存放Mybatis数据库关系映射方法
-
Provider
为Mapper层提供的SQL生成器,即将SQL的生成与映射解耦。 -
Entity
- 根据表结构自动生成实体类
**注意**: i、多次自动生成不会覆盖,如需更新需要把旧的实体类文件删除 ii、有需要的话可以在实体类文件右击generate一键生成构造函数、set get方法、@Autowired等 iii、自动生成的实体类中datetime类型的字段会被转为Java的timestamp类型数据,存储的时候也使用timestamp类型即可 iv、生成出来的实体类catalog = "" 报错,是IDE的报错,不影响使用参考文档:https://blog.csdn.net/chenju05244554/article/details/1009142081
- 根据表结构自动生成实体类
-
Hibernate 和 Mybatis 的对比
- Hibernate优势:
i、DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。 ii、对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。 iii、数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。 iv、有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。 - Mybatis优势:
i、MyBatis可以进行更为细致的SQL优化,可以减少查询字段。 ii、MyBatis容易掌握,而Hibernate门槛较高。 - 选用Mybatis的原因
i、Hibernate无法满足动态获取部分字段的需求,即使是使用Hibernate提供的原始SQL也无法实现 ii、Hibernate的JPA查询只适用于一些简单的情况,如遇到复杂的SQL,Repository中的方法名会很长。这时候又将回到Hibernate的自定义SQL查询,即原生SQL。 iii、Hibernate的维护成本比MyBatis高很多,MyBatis的SQL生成完全取决于开发者,所以SQL修改、维护、优化会比较便利。
- Hibernate优势:
九、MyBatis使用
- 引入MyBatis以及pagehelper分页插件
dependencies { implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.1.1' implementation group: 'com.github.pagehelper', name: 'pagehelper-spring-boot-starter', version: '1.2.10' } - application.properties 添加相关配置
#mybatis设置 mybatis.type-aliases-package=com.duomai.balance_card.Model.Mapper // 项目中Mapper存放的包 mybatis.configuration.map-underscore-to-camel-case=true // 自动将sql字段下划线转为驼峰,可以保证取出的数据格式就是数据库中存储的格式 logging.level.com.duomai.balance_card.Model.Mapper=DEBUG // 开启DEBUG模式,用于开发环境,记录执行的SQL。 #pagehelper插件设置 pagehelper.helperDialect=mysql // 指定分页插件使用哪种数据库 pagehelper.reasonable=false // 分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。 pagehelper.supportMethodsArguments=true // 支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页,设置后无需手动启用分页插件。 pagehelper.params=pageNum=page;pageSize=limit // 自定义Mapper 接口参数来传递分页参数的参数名 - 添加Mapper类扫描的两种方式
- 在启动类中添加对mapper包扫描@MapperScan(推荐使用)
@SpringBootApplication @MapperScan("com.duomai.balance_card.Model.Mapper") - 在具体的Mapper类上面添加注解
@Mapper
- 在启动类中添加对mapper包扫描@MapperScan(推荐使用)
-
Mapper类SQL用例
public interface StoreBalanceCardsMapper { // select用例 @SelectProvider(type = StoreBalanceCardsSqlBuilder.class, method = "getById") List<Map> getById(@Param("id") int id, @Param("fields") String fields, @Param("page") int page, @Param("limit") int limit); // 列表 @SelectProvider(type = StoreBalanceCardsSqlBuilder.class, method = "list") Page<Map> list(@Param("fields") String fields, @Param("page") int page, @Param("limit") int limit); // insert用例 @InsertProvider(type = StoreBalanceCardsSqlBuilder.class, method = "add") @Options(useGeneratedKeys=true, keyProperty="storeBalanceCard.id", keyColumn="id") int add(@Param("storeBalanceCard") StoreBalanceCards storeBalanceCard); // update用例 @UpdateProvider(type = StoreBalanceCardsSqlBuilder.class, method = "updateName") @Options(useGeneratedKeys=true, keyProperty="storeBalanceCard.id", keyColumn="id") int updateName(@Param("storeBalanceCard") StoreBalanceCards storeBalanceCard, @Param("limit") int limit); // delete用例 @DeleteProvider(type = StoreBalanceCardsSqlBuilder.class, method = "deleteById") int delete(@Param("id") int id, @Param("limit") int limit); }- 通过Provider的方式动态获取SQL
-
需要分页时在具体的方法后多加page、limit参数,自动实现分页
// Service层调用 List storeBalanceCard = storeBalanceCardsMapper.list(fields, page, limit); // Mapper层实现 Page<Map> list(@Param("fields") String fields, @Param("page") int page, @Param("limit") int limit); - 分页组件仅可用于查询,不可用于更新/删除,更新/删除需要另外实现
-
Providerle类SQL用例
public class StoreBalanceCardsSqlBuilder { private static final String STORE_BALANCE_CARDS = "store_balance_cards"; public static String getById(int id, String fields) { return new SQL(){ { SELECT(fields); FROM(STORE_BALANCE_CARDS); WHERE("store_balance_cards.id = #{id}"); } }.toString(); } public static String list(String fields) { return new SQL(){ { SELECT(fields); FROM(STORE_BALANCE_CARDS); } }.toString(); } public static String add(@Param("storeBalanceCard") StoreBalanceCards storeBalanceCard) { return new SQL(){ { INSERT_INTO(STORE_BALANCE_CARDS); VALUES("sys_name", "#{storeBalanceCard.sysName}"); VALUES("store_id", "#{storeBalanceCard.storeId}"); VALUES("entity_store_id", "#{storeBalanceCard.entityStoreId}"); VALUES("name", "#{storeBalanceCard.name}"); VALUES("type", "#{storeBalanceCard.type}"); VALUES("item_id", "#{storeBalanceCard.itemId}"); VALUES("photo", "#{storeBalanceCard.photo}"); VALUES("state", "#{storeBalanceCard.state}"); VALUES("upgrade", "#{storeBalanceCard.upgrade}"); VALUES("recharge_discount", "#{storeBalanceCard.rechargeDiscount}"); VALUES("updated_at", "#{storeBalanceCard.updatedAt}"); VALUES("created_at", "#{storeBalanceCard.createdAt}"); } }.toString(); } public static String updateName(@Param("storeBalanceCard") StoreBalanceCards storeBalanceCard, @Param("limit") int limit) { return new SQL(){ { UPDATE(STORE_BALANCE_CARDS); SET("name=#{storeBalanceCard.name}"); if (storeBalanceCard.getUpdatedAt() != null) { SET("updated_at=#{storeBalanceCard.updatedAt}"); } WHERE("store_id = #{storeBalanceCard.storeId}"); } }.toString() + " limit " + limit; } public static String deleteById(int id, int limit) { return new SQL(){ { DELETE_FROM(STORE_BALANCE_CARDS); WHERE("store_balance_cards.id = #{id}"); } }.toString() + " limit " + limit; } } - 分页组件详解
十、接口输出
-
接口统一输出格式
state: // 状态位 msg: // 接口输出提示信息 data: { // 总的接口输出数据,可以为空 data: // 接口数据 other: // 其他返回数据,如total/page_total等 ...: } - 正常接口输出/异常监听输出统一使用
OutPut/ApiReturn方法
十一、异常处理
- 项目运行异常统一监听
现有:ControllrHandler路由异常监听、SqlHandler数据库操作异常监听
需要持续添加 - 统一使用接口输出类进行返回,杜绝直接返回报错信息。
十二、单元测试
-
添加单元测试类
src\test\java\com\duomai\balance_card\BalanceCardApplicationTests.java -
demo
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class BalanceCardApplicationTests { @Autowired private MockMvc mockMvc; @Test @SuppressWarnings("unchecked") public void getCard() throws Exception { String card_id = "1"; String fields = "store_id,item_id,recharge_discount,photo,type,name"; String res = this.mockMvc.perform(get("/card/get") .param("id", card_id).param("fields", fields) ) .andDo(print()) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); // 接口返回不为空 assertThat(res).isNotNull(); // 校验接口返回格式是否完整 Map<String, Object> api_res = Common.jsonToMap(res); assertThat(api_res).isNotNull(); assertThat(api_res).hasSize(3); assertThat(api_res).containsKeys("state", "msg", "data"); // 校验接口返回state是否正确 int state = (int) api_res.get("state"); Map<String, Object> api_data = (HashMap<String, Object>) api_res.get("data"); assertThat(state).isEqualTo(ApiReturnDefines.SUCCESS); // 校验data数据是否正确 assertThat(api_data).containsOnlyKeys("data"); ArrayList<Map> data = (ArrayList) api_data.get("data"); String[] fieldsAll = fields.split(","); assertThat(data).hasSize(1); assertThat(data.get(0)).containsKeys((Object[]) fieldsAll); } } - 详解
- 使用
@RunWith(SpringRunner.class)、@SpringBootTest定义测试类 - 添加注解
@AutoConfigureMockMvc,使用Spring MockMvc模拟Spring的HTTP请求并将其交给控制器,实际上并没有真正地启动服务器,仅仅是Mock,节省了启动服务器的开销。 - 使用
AssertJ库类来验证接口返回内容
在demo中,使用断言验证了接口返回格式、state状态、data数据格式等基本内容。
参考文档:https://spring.io/guides/gs/testing-web/
- 使用
十三、项目打包
- 不同的构建文件
- 普通 jar 包 : 会将源码编译后以工具包(即将class打成jar包)的形式对外提供,此时,你的 jar 包不一定要是可执行的,只要能通过编译,可以被别的项目以 import 的方式调用。
- 可执行 jar 包 : 能通过 java -jar 的命令运行。
- 普通 war 包 : war 是一个 web 模块,其中包括 WEB-INF,是可以直接运行的 WEB 模块。做好一个 web 应用后,打成包部署到容器中。
- 可执行 war 包 : 普通 war 包 + 内嵌容器 。
-
构建可执行 jar 包
- IDE打包
右侧Gradle -> Tasks -> build -> build - 命令行打包
进入项目根目录,执行gradle build
- IDE打包
- 运行可执行 jar 包
java -jar build/libs/balance_card-0.0.1.jar
运行时可带参数,同application.properties中的参数名
例:java -Djava.security.egd=file:/dev/./urandom -jar /opt/ci123/www/html/java/balance_card/build/libs/${jarName}.jar --server.port=80
十四、容器化部署
- 创建docker镜像
- Dockfile 编写
FROM java:8
MAINTAINER duomai
# 设置镜像源
COPY sources.list /etc/apt/sources.list
# 安装扩展
RUN apt-get update && apt-get install -y \
wget \
curl \
vim \
git \
less
# 配置系统时间
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' > /etc/timezone
VOLUME /tmp
# 设置工作目录
ADD . /opt/ci123/www/html/java
WORKDIR /opt/ci123/www/html/java
# 指定输出端口
EXPOSE 80- 快速构建镜像
sh docker/build.sh [version]
- 快速构建镜像
- 环境变量配置
-
配置
#数据库连接配置 application.properties
spring.datasource.url=jdbc:mysql://${APP_DB_HOST:192.168.0.235}:${APP_DB_PORT:3355}/${APP_DB_DATABASE:shop_balances}
spring.datasource.username=${APP_DB_USER:cishop}
spring.datasource.password=${APP_DB_PASSWORD:fuyuan1906}- 启动容器(
docker/run.sh)
- 启动容器(
- 启动参数
-p // 宿主机运行端口
-d // 宿主机项目地址,缺省为执行run.sh的上一级目录
-n // 容器名称
-v // 镜像版本号
-j // jar包版本,缺省为1.0 -
环境变量
# 运行配置
APP_SERVER_PORT // 容器运行jar包的端口
# 数据库配置
APP_DB_HOST
APP_DB_PORT
APP_DB_DATABASE
APP_DB_USER
APP_DB_PASSWORD
# 日志配置
APP_LOG_PATH // 日志输出地址
APP_LOG_SPRING_WEB_LEVEL // 对应logging.level.org.springframework.web,指org.springframework.web这个包下的日志输出等级,默认为ERROR,开发环境可配置为DEBUG
APP_LOG_MYBATIS_LEVEL // 对应logging.level.com.duomai.balance_card.Model.Mapper,指balance_card.Model.Mapper包下的日志输出等级,默认为ERROR,开发环境可配置为DEBUG,开启DEBUG之后可在日志中查看DB操作 -
启动容器
docker run -d \
-e APP_SERVER_PORT=8080 \
-e APP_DB_HOST=192.168.0.235 \
-e APP_DB_PORT=3355 \
-e APP_DB_DATABASE=shop_balances \
-e APP_DB_USER=cishop \
-e APP_DB_PASSWORD=fuyuan1906 \
-e APP_LOG_PATH=/opt/ci123/www/html/java/balance_card/log \
-e APP_LOG_SPRING_WEB_LEVEL=DEBUG \
-e APP_LOG_MYBATIS_LEVEL=DEBUG \
-p $port:80 \
-v $dir:/opt/ci123/www/html/java/balance_card \
--restart=always \
--name $name \
harbor.oneitfarm.com/duomai/java-balance_card:$version \
sh /opt/ci123/www/html/java/balance_card/docker/start.sh -j $jarName
RabbitMq 镜像队列集群搭建
建议食用本文前请先阅读【RabbitMq 普通集群搭建】篇
一、镜像队列集群概念
镜像队列是基于普通集群模式的扩展,普通集群模式下如果某一个节点宕机,该节点下的队列操作将完全失效。而在镜像队列模式下,队列的数据将被复制到所有节点(或者配置过的节点)中,从而保证了一个节点宕机,其余节点也可以正常消费此消息。但此模式下也必然会带来性能下降、内存/磁盘消耗增加、网络IO负担增加等问题,所以镜像队列适用于对高可用要求比较高的系统。
- 每个镜像队列由一个主队列和一个或多个镜像组成。每个镜像队列都有自己的主节点,主队列存放于主节点上。对队列产生的操作将首先应用于队列的主节点,然后传播到镜像节点。包括发布队列、向消费者传递消息、跟踪来自消费者的确认等行为。
- 发布到集群中的消息将被复制到所有的镜像队列中,消费者连接任意节点消费队列实际上都将被连接至主队列的节点上。如果主队列已经确认消费了消息,则其余镜像队列中的消息将被丢弃。
- 如果主队列所在的节点发生异常,默认情况下最“老”的镜像队列将被选举为主队列,当然也可以制定不同的选举策略。
二、配置镜像队列
- 将队列配置成镜像队列需要通过创建
policy来实现。policy包含策略键ha-mode和其对应的键值ha-params(可选)组成。
exactly模式ha-params为count,表示队列的总数量。count表示主队列+镜像队列的总数量,如果count为1,则表示只存在于主队列。如果count为2则表示存在主队列和一个镜像队列,以此类推。如果count值大于集群中节点的总数则表示所有节点都将同步一份镜像队列。如果某个镜像队列的节点宕机,则会寻找一个剩余未同步的节点来同步镜像队列。
all模式- 不需要
ha-params。 - 此模式下所有节点都将同步镜像队列,如果有新节点加入,则新节点也会进行同步。官方建议同步镜像队列的节点数为
N/2 + 1,其中N表示节点总数。同步到所有节点会增加所有集群节点的负载,包括网络I/O、磁盘I/O和磁盘空间的使用等。
- 不需要
nodes模式ha-params为node names,节点的名称。- 此模式下将在指定的节点上同步镜像队列,如果声明队列时其余节点均不在新,则只会在声明连接的那个节点上创建队列。
- 配置
policy
rabbitmqctl命令配置set_policy [-p vhost] [--priority priority] [--apply-to apply-to] name pattern definitionname:策略名称pattern:策略匹配符,正则表达式。当与给定资源匹配时,将应用该策略。definition:策略内容定义,JSON字符串。priority:策略的优先级,整数。数字越大,优先级越高。默认值为0。apply-to:策略应用的对象,支持queues,exchanges,all,默认值为all// exactly模式,匹配前缀为two的资源 rabbitmqctl set_policy -p cluster ha-two ^two. '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}' // all模式,匹配所有前缀 rabbitmqctl set_policy -p cluster ha-all ^ '{"ha-mode":"all"}' // nodes模式,匹配所有前缀 rabbitmqctl set_policy -p cluster ha-nodes ^ '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'
management管理后台配置Admin -> Policies -> Add / update a policy
- 新镜像队列同步设置
-
ha-sync-mode:manual默认模式,新队列镜像将不同步现有消息,只接收新消息。当消费者消费完所有仅存在于主队列上的消息后,新的队列镜像将随着时间的推移成为和主节点队列相同的精确镜像队列。
-
ha-sync-mode: automatic当新队列镜像加入时,队列将自动同步所有消息。
三、额外说明
- 独占队列不会被复制为镜像队列。
- 主节点队列失效后,其中一个镜像队列将被选举为主队列并带来如下影响:
- 与主机点连接的客户端将全部断开。
- 运行时间最长的镜像队列将被选举为主队列,如果镜像队列尚未开始同步,则队列上的消息将丢失。
- 新的主队列会认为之前所有的消费者的连接都已经断开,它将重新发送旧队列中没有收到
ack的消息。这可能出现客户端已经发送过ack, 但是服务端在接收到之前就已宕机的情况,从而导致发送两遍相同的消息,因此所有未确认的消息都将使用redelivered标志重新发送。 - 如果消费者连着的是镜像队列节点,并且消费者在启动时设置了
x-cancel-on-ha-failover参数,则消费者将收到一个服务端消费取消的通知,如果未设置此参数,则消费者将无法感知主节点已宕机。 - 如果使用自动
ack机制则消息将丢失。
- 如果停止了某个包含主节点队列的节点,则其他节点的镜像队列将被选举为主节点队列。在重新启动此节点后,该节点只会被当做是一个新加入集群的节点,不会重新成为主节点队列。
- 在主节点队列宕机并且其他镜像队列尚未同步的极端情况下,
rabbitMq集群将拒绝任何镜像队列选举为主队列,整个队将不可用且被关闭。如果在镜像队列尚未同步的情况下也需要将某个镜像队列选举为主队列,需要配置policy中ha-promote-on-shutdown为always(默认为when-synced),并且ha-promote-on-failure不可配置为hen-synced(默认值为always)。
四、集群测试
-
启动节点、创建用户、
vhost、exchange、queue,启动消费者。(快速启动,参考【RabbitMq 使用docker搭建集群】篇)- 节点:
- 节点一:
rabbit@clusterRabbit1 - 节点二:
rabbit@clusterRabbit2
- 节点一:
- 用户:
api_management(administrator标签,开放虚拟主机cluster所有权限) -
策略:
rabbitmqctl set_policy -p cluster ha-all ^ '{"ha-mode":"all"}' vhost:cluster-
exchange:cluster(直连交换机) -
queue:- 节点一
rabbit@clusterRabbit1:- 队列一:
name:clusterRabbit1Queue1routing_key:clusterRabbit1key
- 队列二:
name:clusterRabbit1Queue2routing_key:clusterRabbitCommonKey
- 节点二
rabbit@clusterRabbit2:- 队列三:
name:clusterRabbit2Queue1routing_key:clusterRabbit2key
- 队列四:
name:clusterRabbit2Queue2routing_key:clusterRabbitCommonKey(与队列二 相同)
- 节点一
consumer:- 消费者一:
- 连接节点:节点一
rabbit@clusterRabbit1 - 消费队列:队列一
clusterRabbit1Queue1- 消费者二:
- 连接节点:节点一
rabbit@clusterRabbit1 - 消费队列:队列二
clusterRabbit1Queue2- 消费者三:
- 连接节点:节点二
rabbit@clusterRabbit2 - 消费队列:队列三
clusterRabbit2Queue1- 消费者四(非此节点的队列):
- 连接节点:节点一
rabbit@clusterRabbit1 - 消费队列:队列三
clusterRabbit2Queue1- 消费者五:
- 连接节点:节点二
rabbit@clusterRabbit2 - 消费队列:队列四
clusterRabbit2Queue2
- 节点:
-
连接节点二
rabbit@clusterRabbit2,再次声明队列名和队列一同名的队列clusterRabbit1Queue1。【同名队列再次声明】
- 结果:声明不成功,节点二中没有生成新的
clusterRabbit1Queue1队列,而节点一中clusterRabbit1Queue1队列多出了新绑定的routing_key:clusterRabbit2key,这意味着在同一集群中不同节点之间的队列名是唯一的,在一个节点中可以操作另一个节点的队列数据。
- 生产者连接节点一
rabbit@clusterRabbit1,发布clusterRabbit1key消息。【发布此节点队列消息】
- 消费者一 成功接收到消息。
- 生产者连接节点二
rabbit@clusterRabbi2,发布clusterRabbit1key消息。【发布非此节点队列消息】
- 消费者一 成功接收到消息。
- 生产者连接节点一
rabbit@clusterRabbit1,发布clusterRabbitCommonKey消息。【多个节点队列绑定相同消息】
- 消费者二 成功接收到消息。
- 消费者五 成功接收到消息。
- 生产者连接节点一
rabbit@clusterRabbit1,发布clusterRabbit2key消息。【消费非此节点队列消息】
- 消费三、四 轮询接收到消息。
- 关闭节点二
rabbit@clusterRabbit2,连接节点一rabbit@clusterRabbit1,发布clusterRabbit2key消息【投递消息给集群中意外退出的节点】
- 连接节点二
rabbit@clusterRabbit2的消费者全部断开,消费者四并未断开。 - 节点一
rabbit@clusterRabbit1晋升成为队列三、队列四的主节点。 - 消费者四 可以继续成功接收到消息。
- 再次启动节点二
rabbit@clusterRabbit2,没有再次成为队列三、队列四的主节点,只是复制了队列三、队列四的镜像队列。
- 修改消费者四,在
ack之前sleep(20),发布clusterRabbit2key消息,并立即关闭节点二rabbit@clusterRabbit2
- 和上面一样,消费者四并未断开。
- 消费者收到两条相同的消息,说明节点二在收到
ack之前宕机后,此消息会被当做未消费的消息重新放入队列后消费。
使用pm2增删改常驻进程脚本
业务中使用pm2来管理MQ的消费者(需常驻),使用pm2有个好处就是,如果进程意外退出了,pm2可以将它自动重启。下面介绍常见的使用方法:新增、删除、修改。
一、新增一个常驻进程
1.1 先创建一个json配置文件,比如:
{
/**
* docs: http://pm2.keymetrics.io/docs/usage/application-declaration/#attributes-available
*/
name: "cront/Trade/deliveyNotice",
args: "cront/Trade/deliveyNotice",
script: "/opt/ci123/www/html/api_shop/webroot/index.php",
exec_interpreter: "/opt/ci123/php/bin/php",
exec_mode: "fork",
max_memory_restart: "100M",
out_file: "/tmp/api_shop/Delivery.log",
error_file: "/tmp/api_shop/Delivery.log",
instances: 1
}
我们使用代码位置(cront/Trade/deliveyNotice)作为name,方便管理。args是在执行script时加在其后面的参数。
1.2 启动脚本
pm2 start /path/to/your/config.json
start后接上一步写的json配置文件。
二、删除常驻进程
2.1 首先找到我们要删除/关闭的进程在pm2里的id是多少。
// e.g. pm2 list|grep cront/Trade/deliveyNotice
pm2 list|grep xxx
pm2 list可以查看所有pm2管理的进程,我们根据关键字把需要删除的进程筛选出来,找到id。
2.2 从pm2中删除进程
pm2 delete id
id是上一步找到的进程id(进程在pm2中的id,不是pid)。
三、修改常驻进程,并重启
pm2有restart和reload命令。
一般认为restart的意思是先关闭旧进程,再重启一个新的进程。
而reload的意思是先重启一个新的进程,再关闭旧进程。
pm2 restart /path/to/your/config.json
#或者
pm2 reload /path/to/your/config.json
restart和reload都不能真正实现进程的平滑重启。只有将消费进程改造后,才可以实现平滑重启。
四、其他
4.1 pm2也支持将所有config写在一个json文件里;
4.2 使用pm2管理常驻进程的初衷,是想借助pm2的进程重启功能;其他一些工具比如supervisor也有类似功能。
4.3 pm2管理非node进程时,只能使用fork模式,不能使用cluster模式;
4.4 官方对配置文件的说明: Process File;