Gặp khó khăn khi deploy Nodejs lên Digital Ocean
Có phải bạn đang dùng thẳng file index khi build ra từ React
không vậy ??
Tự động thêm text khi chạy
Bạn check trong file master blade xem có viết thừa chữ logi
ở đâu không. Như này là bạn đang in thừa biến ở đâu đó rồi
Nhờ mọi người giải thích giúp mình Redux-Saga
Cách viết while (true)
phương pháp giúp bạn controll flow chạy khác so với cách viết thông thường mà bạn đang dùng. Với cách viết thông thường là định nghĩa 1 cái saga
sau đó gán 1 action cho saga đó bằng cách dùng takeEvery
hoặc takeLatest
thì với mỗi action được dispatch nó sẽ liên tục gọi đến cái function saga
mà bạn định nghĩa. Còn đối với việc sử dụng while (true)
thì thay vì bạn gán trực tiếp 1 action
cho 1 function saga
như này:
export function* watchFetchPost() {
yield takeEvery(types.FETCH_POST, workFetchPost);
}
Thì while (true)
sẽ chạy như 1 vòng lặp vô tận và bên trong đó bạn sẽ sử dụng hàm take
để lắng nghe các action
được tạo ra. Một ví dụ cụ thể hơn mình lấy từ chính doc của Redux-saga
như sau:
import { take, call, put } from 'redux-saga/effects'
import Api from '...'
function* authorize(user, password) {
try {
const token = yield call(Api.authorize, user, password)
yield put({type: 'LOGIN_SUCCESS', token})
return token
} catch(error) {
yield put({type: 'LOGIN_ERROR', error})
}
}
function* loginFlow() {
while (true) {
const {user, password} = yield take('LOGIN_REQUEST')
const token = yield call(authorize, user, password)
if (token) {
yield call(Api.storeItem, {token})
yield take('LOGOUT')
yield call(Api.clearItem, 'token')
}
}
}
Khác với cách viết thông thường là mỗi khi bạn tạo ra một action là LOGIN_REQUEST
thì nó sẽ lập tức chạy ngay một cái saga
nào đó mà bạn gán cho actions
đó như này:
export function* login() {
yield takeEvery(types.LOGIN_SUCCESS, doSomeLogin);
}
- Thì với cách viết dùng
while
như trên thì cho dù bạn có tạo ra có click cái button để tạo ra actionLOGIN_REQUEST
bao nhiêu lần thì nó cũng sẽ chỉ nhận đúng lần đầu tiên thôi và không chạy liên tục như cách viết thông thường. - Thứ 2 là vì đây là vòng lặp (lặp vô hạn) nên sau khi bạn chạy xong cái
LOGIN_REQUEST
thì cái sagaloginFlow
này nó sẽ không lắng nghe thêm bất cứ actionLOGIN_REQUEST
nào nữa mà chỉ đợi actionLOGOUT
mà thôi vì là vòng lặp mà bạn chưa chạy hết nên nó không chạy lại từ đầu được :v - Cuối cùng sau khi bạn gọi action
LOGOUT
thì nó mới kết thúc 1 vòng lặp và mới tiếp tục lắng nghe lại cái actionLOGIN_REQUEST
. Mọi thứ cứ tiếp diễn như thế mãi mãi.
=> Ở đây 2 action LOGIN_REQUEST
và LOGOUT
sẽ luôn đi kèm với nhau theo đúng thứ tự trên dưới và chỉ có thể gọi lại khi mà thằng kia đã được gọi như sau:
LOGOUT
sẽ luôn phải đợi 1 actionLOGIN_REQUEST
chạy trước nó và chạy xongLOGIN_REQUEST
sau khi chạy 1 lần cũng phải đợi cho đến khiLOGUT
chạy xong thì nó mới chạy lại được
Bạn có thể thử xem ví dụ này để hiểu hơn về dfung cái while()
: https://codesandbox.io/s/pulling-future-action-e7fll
Ví dụ trên chỉ có 2 action là INCREMENT
và DECREMENT
và như mình nói ở trên cho dù bạn click bao nhiêu lần INCREMENT
liên tiếp đi nữa thì nó cũng chỉ chạy duy nhất lần đầu và nó chỉ chạy lần tiếp theo khi mà bạn ấn DECREMENT
. Tương tự thì DECREMENT
cũng sẽ không chạy nếu bạn chưa bấm INCREMENT
.
Còn về phần fork
thì bạn có thể hiểu như sau cái hàm:
yiedl call()
Trong redux-saga nó là một hàm blocking nghĩ là với ví dụ dừ doc của redux-saga
:
function* loginFlow() {
while (true) {
const {user, password} = yield take('LOGIN_REQUEST')
const token = yield call(authorize, user, password)
if (token) {
yield call(Api.storeItem, {token})
yield take('LOGOUT')
yield call(Api.clearItem, 'token')
}
}
}
Trong trường hợp đang chạy cái API ở phần const token = yield call(authorize, user, password)
nhưng tự nhiên người dùng lại muốn LOGOUT
luôn thì như mình nói ở trên là không được vì cái call()
kia là blocking nên code ở dưới sẽ không chạy cho đến khi nó chạy xong. Chính vì thế mặc dù ta bấm LOGOUT
rồi nhưng nó sẽ bị bỏ qua cho đến khi chạy thành công việc gọi API. Chính vì thể ở đây ta cần dùng hàm fork()
như sau:
import { fork, call, take, put } from 'redux-saga/effects'
import Api from '...'
function* authorize(user, password) {
try {
const token = yield call(Api.authorize, user, password)
yield put({type: 'LOGIN_SUCCESS', token})
yield call(Api.storeItem, {token})
} catch(error) {
yield put({type: 'LOGIN_ERROR', error})
}
}
function* loginFlow() {
while (true) {
const {user, password} = yield take('LOGIN_REQUEST')
yield fork(authorize, user, password)
yield take(['LOGOUT', 'LOGIN_ERROR'])
yield call(Api.clearItem, 'token')
}
}
Khi chúng ta dùng hàm fork
thì cái task gọi API kia sẽ đươc tiến hành chạy ở background và không làm chặn cái luồng chạy chính của chúng ta nữa chính ví thể code bên dưới có thể tiếp tục chạy mà không cần đợi phần gọi API chạy xong. Bạn có thể hiểu nôm na là khi dùng fork
thì cái công việc mà bạn truyền vào trong đó sẽ được mang ra một chỗ khác để thực hiện chứ không thực hiện ở ngay đó nữa.
Phỏng vấn php
Theo mình khi đi phỏng vấn về mảng web nói chung không chỉ PHP thì bạn cần chuẩn bị kĩ các kiến thức cơ bản như:
- OOP: 4 tính chất cơ bản của lập trình hướng đối tượng, giải thích + ví dụ
- Database: cách xác định quan hệ giữa các bảng, vài cách query dữ liệu cần thiết
- Web-base: như HTML/CSS/JS
- Cấu trúc dữ liệu giải thuật
Về PHP thì bạn cần hiểu về một số khái niệm như Session, Cookie, mô hình MVC, ... . Cụ thể thì bạn có thể tìm hiểu về các câu hỏi trên mạng để tham khảo thêm. Nhưng cơ bản cho dù bạn phỏng vấn vị trí web dev PHP, NodeJS hay Python thì vẫn phải nắm được những kiến thức web-base cơ bản và cả kiến thức về ngôn ngữ bạn apply nữa. Bạn có thể tham khảo thêm ở đây:
Lỗi không đọc được tiếng Nhật khi import CSV khi dùng maatwebsite/excel trong Laravel
Bạn có thể show cái config của nó không ?? config/excel.php
Xử lý khi click outside select2 multiple
Nếu mình nhớ không nhầm thì mỗi lần bạn click chọn dữ liệu xong thì nó đề close cái dropdown chọn mà nhỉ. Nếu bạn muốn bắt cái event sau khi chọn xong thì nó sẽ là select2:close
hoặc select2:closing
. Bạn có thể tham khảo tại đây:
- Danh sách event: https://select2.org/programmatic-control/events
- Demo: https://jsfiddle.net/jtw17r8h/2/
Lỗi khi kết nối Laravel và MongoDB
- Hình như ở đây bạn bị sai về cú pháp rồi, để query từ bảng thì bạn dùng
DB::table('admin')
còn nếu bạn muốn dùng đến các hàm củaCollection
trong Laravel thì bạn phải thực hiện query ra kết quả rồi và dùng nó với biến$data
dạng như này nhé$data->pluck('id')->all()
. - Trong trường hợp bạn dùng
MongoDB
thì cần tải thêm driver riêng choLaravel
là https://github.com/jenssegers/laravel-mongodb. - Còn nếu bạn dùng 2 loại database cùng lúc thì với mỗi model thì bạn cần khai báo phần connection cho nó nhé https://laravel.com/docs/5.8/eloquent#eloquent-model-conventions mục
Database Connection
Iframe trên Iphone 6S Plus
bạn thử làm theo cách này xem https://stackoverflow.com/questions/43262156/cant-scroll-iframe-on-mobile-ios-safari
Set value trong Form [Antd]
Khi bạn tách thành component riêng thì sẽ phải chủ động truyền value vào cho phần input của bạn nhé:
return <Input value={this.props.value} />;
Làm sao để làm được ảnh ảnh gif như hình dưới đây
bạn thử search "gif capture for window" xem, có rất nhiều tool có thể quay lại màn hình và xuất ra gif nhé bạn
Làm sao để phân biệt text để apply kiểu font
trong trường hợp ngôn ngữ lẫn lộn như trên của bạn chỉ có cách duy nhất là tạo 1 global class và add vào các vị trí cần thôi như hiện tại thôi chứ không có cách nào để tự detect được content để chọn font chữ đâu )
Em xin hỏi vấn đề về setup database trên ASP.NET mvc để mọi người cùng phát triển
mình không dành về ASP.NET lắm nhưng theo mình nhớ bên đó có cái gọi là Entity Framework
với cách làm tên là Code First
cũng tương tự Migration bên Laravel thì phải. Bạn thử tìm hiểu xem
Tìm file đã được backup mới nhất trong thư mục backup sử dụng php như thế nào?
@mediavn thế thì đơn giản nhất bạn có thể lấy thông tin về thời gian update từng folder và so sánh với nhau. Cách lấy thì tương tự như ở đây https://viblo.asia/q/lay-thong-tin-thoi-gian-cua-file-trong-thu-muc-su-dung-laravel-o754DoPW5M6
Lấy thông tin thời gian của file trong thư mục sử dụng laravel
<?php
$backupFile = 'test.dmp';
$day = date("D");
$filePath = "d:/backup/$day/$backupFile";
if (file_exists($filePath)) {
echo "$filePath was last modified: " . date("F d Y H:i:s.", filemtime($filePath));
}
else {
echo 'File not found';
}
đây nha bạn.
Phân trang trong Lavarel
Ở đay bạn có thể làm theo 2 cách:
- Như cách bạn đang làm hiện tại thì khi bấm vào edit bạn có thể gửi thêm 1 cái
?current_page=2
. Bên trong function mà return cái view edit bạn có thể viết:
public function edit(Post $post, Request $request)
{
$currentPage = $request->query('current_page');
return view('post.edit', compact('post', 'currentPage);
}
Bên giao diện phần edit bạn có thể chèn thêm biến này vào một ô input và ẩn nó đi bằng css:
<form>
<input value="{{ current_page }}" name="current_page" style="visibility: hidden" />
</form>
Sau khi bạn nhập dữ liệu edit xong và gửi lên thì bên trogn hàm xử lý bạn có thể viết thành:
public function update(Post $post, Request $request)
{
// Đoạn code xử lý việc update bài viết
// Sau khi update thành công
$currentPage = $request->input('current_page');
$link = route('tên-router-trang-danh-sach-của-bạn') . '?page=' . $currentPage;
return redirect($link);
}
- Sử dụng ajax: khi bạn cập nhật thông tin thì sẽ gửi dữ liệu lên qua ajax sau đó lấy kết quả đó update lại vào phần bạn vừa edit. Với cách làm này thì khi chọn edit thì bạn có thể hiện thị 1 cái modal phục vụ cho việc edit
Phân biệt cú pháp : require và import
require
và module.exports
là cách thức mặc định để bạn sử dụng một thư viện hay một module trong javascript (CommonJS)
hay NodeJS
. Còn cú pháp bạn thấy trong React
là import
hay export
là các tính năng mới của ES6
. Tuy nhiên trên thực tế thì lúc bạn dev thì sẽ dùng import
, export
như bạn nói trên còn khi bạn thực hiện build ra code production thì toàn bộ code của bạn sẽ chạy qua các bộ complie như Babel
và nó sẽ chuyển cú pháp import
và export
về dạng require
và module.exports
thông thường. NodeJS
bạn cũng có thể sử dụng import
và export
, cụ thể bạn có thê tham khảo ở đây https://nodejs.org/api/esm.html (lưu ý đây vẫn là tính năng thử nghiệm)
Một vài câu hỏi liên quan đến Reactjs/Nextjs mong được giải đáp
Với cách làm như hiện tại của bạn thì nó có thể (có thể thôi) dẫn tới một số vấn đề liên quan đến security, cụ thể bạn tham khảo ở đây https://dev.to/rdegges/please-stop-using-local-storage-1i04. Tuy nhiên nếu ứng dụng của bạn không quá quan trọng việc này thì bạn có thể xử lý như sau:
- localStorage chỉ lưu duy nhất token. Mỗi lần khi người dùng truy cập vào ứng dụng của bạn thì mới gọi API để lấy thông tin về user ứng với token đó và lưu nó vào trong Redux hoặc Context của React. Bằng cách này thì mỗi lần user truy cập vào ứng dụng thì nó sẽ lấy thông tin mới nhất về role trên server để sử dụng.
- Mình chưa làm việc nhiều với NextJS nên không rõ có boilerplage nào không nhưng chắc bạn search github xem cái nào nhiều người dùng thì có thể sử dụng theo. Còn nvieejc bạn chuyển ứng dụng React thông thường hiện tai sang NextJS thì chắc sẽ phải cập nhật khá nhiều config ban đầu cũng như code vì đơn giản nhất là bên NextJS là SSR nên nó sẽ không có cái react-router mà bạn dùng bên React mà nó sẽ có config sẵn phần này theo tên folder và cách đặt tên
Đường dẫn tuyệt đối trong rect
Nếu bạn làm việc với React thì mình nghĩ đầu ra của bạn sẽ là 1 single page application (SPA) rồi thi việc url giữa các trang sẽ do react-router quản lý cho bạn rồi nên bạn sẽ không phải lo đến vấn để thẻ <a> hay chính xác là component <Link /> bị trỏ sai so với khi dev. Còn với ảnh thì nếu bạn import ảnh theo cú pháp:
import logo from './images/logo';
const Logo = () => <img src={logo} />
Thì khi build ảnh của bạn sẽ được chuyển về dạng base64 và không phụ thuộc vào đường dẫn nữa. Ví dụ mình có 1 ảnh như sau:
Và sử dụng cú pháp như mình nói ở trên thì trong code khi build ra ảnh của bạn sẽ được chuyển về dạng như này và sử dụng trên trang web của bạn:
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBkZWZhdWx0IHF1YWxpdHkK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgA+gHCAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9MooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAopvmx+Z5e9fMxnbnnH0p1ABRRTXkjjKh3VSxwoJxk+1ADqKKKACiiigAooqqmpWcmpSaclwjXkab3hB+ZV45/UfnQBaooooAKKKKACiiigAooooAKKKKACiiqsupWcF/DYy3CLdTAmOI9WAz0/I0AWqKKKACiiqtrqVne3Fzb21wksts22ZVPKHng/kfyoAtUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAcN/zWH/ty/pXc1w3/ADWH/ty/pXc0AFcR48/5DPhf/r+H/oSV29cR48/5DPhf/r+H/oSUAdvRUVzN9ntJpgNxjRnx64Ga5W38eW8uiWl2bOR767ZlhsYG3u2CRnOOBx1x+eKAOvorkW1Xxsy+anh60VOvlNcgvj65Aq5oPiuLVruTT7u1k0/Uo+TbTH7w9VPGf89aAOiriNN/5K9q/wD14r/7Srt64jTf+Svav/14r/7SoA7eiiud1rxZDpt8um2VpLqO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HHFLRQByI8QeJdM/d6r4ea6A/wCW+ntuDe+3r/Kj/hO0YFYvD2uPL/c+y/8A1z/KuuooA4a4sdf8ZzQx6lajStHRw7wl8yzY7H0/THvXbxxpFGscahUQBVUdgKdRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/9k=
Nếu bạn không tin có thể copy đoạn base64 trên và đem vào lên 1 trang convert base64 nào đó sang image để thử. Một điều nữa là với cách build thông thường thì React sẽ mặc định khi vào domain http://doman.com/
sẽ đi vào ngay file index chứa phần code js và css đã được build. Tuy nhiên nếu bạn đặt theo kiểu http://doman.com/spa
mới là trang có chứa code js và css từ react thì trước khi build bạn cần mở file package.json
trong create-react-app
lên và thêm mục sau vào:
{
"homepage": "http://doman.com/spa",
...
}
Để quá trình build sẽ tự động setup lại đường dẫn cho bạn.
Hỏi cách config nginx cho nhiều project trên cùng domain
Bạn có thể config theo kiểu reverse proxy như sau. Giả sử bạn có 2 project với đường dẫn như sau:
- project1 - /var/www/demo/project1/public
- project2 - /var/www/demo/project2/public
Thì bạn có thể có file config như sau đối với project PHP (ở đây hình như bạn cũng dùng Laravel):
# config cho project 1
server {
listen 9001 default_server;
root /var/www/demo/project1/public;
index index.php index.html index.htm;
try_files $uri $uri/ @rewrite;
location @rewrite {
rewrite ^/(.*)$ /index.php;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
include fastcgi_params;
fastcgi_pass unix:///var/run/php/php7.3-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param REALPATHTEST $realpath_root;
internal;
}
}
# config cho project 2
server {
listen 9002 default_server;
root /var/www/demo/project2/public;
index index.php index.html index.htm;
try_files $uri $uri/ @rewrite;
location @rewrite {
rewrite ^/(.*)$ /index.php;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
include fastcgi_params;
fastcgi_pass unix:///var/run/php/php7.3-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param REALPATHTEST $realpath_root;
internal;
}
}
# config reversy proxy
server {
listen 80;
listen [::]:80;
server_name dev.demo.api;
access_log /var/log/nginx/dev.access.log;
error_log /var/log/nginx/dev.error.log;
location ~ /project1/(.*)$ {
proxy_pass http://localhost:9001/$1?$query_string;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location ~ /project2/(.*)$ {
proxy_pass http://localhost:9002/$1?$query_string;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Bạn có thể thử như cách trên và chỉ cần đổi lại phần đường dẫn của project 1 và 2 theo folder của bạn rồi restart lại nginx xem. Với cấu hình trên thì mọi request sẽ đi vào domain của bạn và tùy vào đường dẫn nó sẽ tự map được vào project tưởng ứng của bạn và truyền được hết thông tin về query string sang. Bạn có thể tham khảo thêm về set-up reverse proxy tại đây https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
Mã hoá objectId trong mongodb
Theo mình bạn không nhất thiết phải mã hóa cái objectId
đó mà thay và đó bạn có thể tạo thêm một cột mới trong database gọi là hashId
. Với cách làm nay bạn sẽ đạt được mục đích mình mong muốn và thay vì mất công mã hóa hay giải mã bạn chỉ cần đổi:
link-bai-viet-objectId
=>link-bai-viet-hashId
Về cách tạo hashId
thì có rất nhiều cách bạn có thể làm miễn sao cho hashId
sinh ra là unique không khi bạn query từ database sẽ bị trùng, ví dụ bạn có thể lấy tiêu đề bài viết đem đi dùng với hàm băm như md5()
, sha
, ... và cắt lấy n kí tự đầu. Trong trường hợp tiêu đề bài viết giống nhau thì bạn có thể sử dụng thời gian tạo bài và áp dụng phương pháp tương tự. Ví dụ với PHP:
<?php
$title = 'Mã hóa objectId trong mongodb';
$hash_title = md5($title); // 03a9f61292bc28aeadce9b9ebd464359
$hash_id = substr($hash_title, 0, 7) // Lấy 7 kí tự đâu => 03a9f61
``