BRP
- Booking Registration Platform
Vue3
,Vite
,TypeScript
,vue-router
,Pinia
,elementplus
,AXIOS
,scss
IRIS2023
,REST
,统一入口,统一错误处理,ORM
,JWT
,自研框架,后端处理上传文件Centos7.9
,Ngnix
,创建独立命名空间,创建独立数据库,创建独立Applicaion
,三库分离,打包前端文件,跨域问题,创建灾备服务器,备份数据库,恢复数据库,将数据库加入MIRROR
vscode
,IRIS2023
SVG
矢量图使用,路由鉴权,封装全局组件,HTTP
上传图片,使用Git
进行版本控制,上传Github
由于篇幅有限,以下为重点功能讲解,可参考项目源码对比观看。
PNPM
- 使用VSCODE
- 安装插件node.js
与npm
。下一步安装即可。
C:\Users\hp>node -v
v16.14.0
C:\Users\hp>npm -v
8.3.1
cmd
,安装pnpm
。npm install -g pnpm
注:网络不好可以替换镜像地址。
获取当前配置的镜像地址
pnpm get registry
设置新的镜像地址
pnpm set registry https://registry.npm.taobao.org
cmd
,创建vue3
项目。依次填写名称 - brp-app-web
,Vue
,TypeScript
。E:\vue>pnpm create vite
../.pnpm-store/v3/tmp/dlx-13224 | +1 +
../.pnpm-store/v3/tmp/dlx-13224 | Progress: resolved 1, reused 0, downloaded 1, added 1, done
√ Project name: ... brp-app-web
Select a framework : Vue
Seiect a variant : TypeScript
vscode
打开项目,在terminl
中输入pnpm run dev
即可启动项目。vscode
推荐使用v3
插件如下:Element UI Snippets - v0.7.2
Prettier - Code formatter - v10.1.0
TypeScript Vue Plugin (Volar) - v1.8.26
Wink Snippets - v1.0.5
main.ts
文件中引入必要的依赖包import { createApp } from "vue";
import "@/style/reset.scss";
import App from "@/App.vue";
import router from "@/router";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
//@ts-ignore
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import pinia from "@/store";
const app = createApp(App);
app.use(router);
app.use(ElementPlus, {
locale: zhCn,
});
app.use(pinia);
app.mount("#app");
首页分为顶部,底部,中间内容部分:BANNER区,搜索区,筛选区,表格,分页,右侧。
scss
左右布局main.ts
中...
import HosptialTop from "@/components/hospital_top/index.vue";
import HosptialBottom from "@/components/hospital_bottom/index.vue";
...
const app = createApp(App);
app.component("HosptialTop", HosptialTop);
app.component("HosptialBottom", HosptialBottom);
app.mount("#app");
注:再使用ts
中有代码提示报错,运行时没有报错,可以使用//@ts-ignore
忽略。
App.vue
中引入全局组件,该文件为入口文件。<template>
<div class="container">
<HosptialTop />
<div class="content"><router-view>router-view>div>
<HosptialBottom />
<Login />
div>
template>
scss
样式举例。注:使用scss
前需要先安装scss
在项目目录中使用cmd
执行pnpm i scss
命令即可。
top
浮动 display: flex;
居中 justify-content: center;
并在位置固定position: fix;
层级在最上z-index: 999;
content
中让left
与right
浮动起来,在左右两侧justify-content: space-between;
注:后面的前端界面中有大量左右布局均为此方式,后面不再赘述。
vue-router
router/index.ts
编写路由。注:使用vue-router
时需要在项目目录中打开cmd
执行命令pnpm i vue-router
安装路由。
createRouter
为创建路由配置方法。scrollBehavior()
方法每次路由跳转滚动条自动回到顶部。component: () => import("@/pages/home/index.vue"),
懒加载路由,import
中文件为路由的路径。import { createRouter, createWebHistory } from "vue-router";
export default createRouter({
history: createWebHistory(),
routes: [
{
path: "/home",
component: () => import("@/pages/home/index.vue"),
meta: {
title: "首页",
},
},
],
scrollBehavior() {
return {
left: 0,
top: 0,
};
},
});
main.ts
中引入路由。...
import router from "@/router";
...
app.use(router);
...
BANNER
- 走马灯效果 - 使用elementplus
创建组件pages
- carousel
- index.vue
。
使用组件el-carousel
,循环4次,设置img
图片即可。
<el-carousel height="350px">
<el-carousel-item v-for="item in 4" :key="item">
<img src="@/assets/images/web-banner-1.png" alt="" />
el-carousel-item>
el-carousel>
创建组件pages
- search
- index.vue
。
使用组件el-autocomplete
:fetch-suggestions="fetchData"
为搜索时防抖的触发方法根据输入内容进行后台查询。 <el-autocomplete
clearable
placeholder="请输入医院名称"
v-model="hosname"
:fetch-suggestions="fetchData"
:trigger-on-focus="false"
@select="goDetail"
/>
::v-deep()
修改组件内部的样式。注:如果去掉scoped
关键字,则样式为全局样式。
AXIOS
发起请求utils
- request.ts
,编写代码如下。注:需要安装axios
库pnpm i axios
。
axios.create
方法创建一个axios
实例:可以设置基础路径、超时的时间的设置。axios
请求、响应拦截器功能,请求拦截器,一般可以在请求头中携带公共的参数token
,响应拦截器,可以简化服务器返回的数据,处理http
网络错误。axios
。import axios from "axios";
import { ElMessage } from "element-plus";
import userUserStore from "@/store/modules/user";
const request = axios.create({
//baseURL: import.meta.env.VITE_API_BASE_URL,
baseURL: "/api",
timeout: 5000,
});
request.interceptors.request.use((config) => {
let userStore = userUserStore();
if (userStore.userInfo.token) {
config.headers.token = userStore.userInfo.token;
}
return config;
});
request.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
let status = error.response.status;
console.log(status);
switch (status) {
case 404:
ElMessage({
type: "error",
message: error.message,
});
break;
case 500 | 501 | 502 | 503 | 504 | 505:
ElMessage({
type: "error",
message: "服务器挂了",
});
break;
case 401:
ElMessage({
type: "error",
message: "参数有误",
});
break;
}
return Promise.reject(new Error(error.message));
}
);
export default request;
pages - home - level - index.vue
与pages - home - region- index.vue
ul>li
,样式浮动,使用flex
按照比例划分区域。等级:占0.5
份,其他占9
份,并增加鼠标滑过样式。 .content {
display: flex;
.left {
margin-right: 10px;
flex: 0.5;
}
.hospital {
flex: 9;
display: flex;
li {
margin-right: 10px;
&.active {
color: #55a6fe;
}
}
li:hover {
color: #55a6fe;
cursor: pointer;
}
}
}
TypeScript
类型api - home - type.ts
注:表格的样式与绑定数据不再赘述,参考elementUI
官方即可。
ResponseData
- 定义首页模块ts
数据类型Hospital
- 代表已有的医院数据的ts
类型Content
- 存储全部已有医院的数组类型HospitalResponseData
- 获取已有医院接口返回的数据ts类型export interface ResponseData {
code: number;
message: string;
ok: boolean;
}
export interface Hospital {
id: string;
createTime: string;
updateTime: string;
isDeleted: number;
params: {
hostypeString: string;
fullAddress: string;
};
hoscode: string;
hosname: string;
hostype: string;
provinceCode: string;
cityCode: string;
districtCode: string;
address: string;
logoData: string;
intro: string;
route: string;
status: number;
bookingRule: {
cycle: number;
releaseTime: string;
stopTime: string;
quitDay: number;
quitTime: string;
rule: string[];
};
}
export type Content = Hospital[];
export interface HospitalResponseData extends ResponseData {
data: {
content: Content;
pageable: {
sort: {
sorted: boolean;
unsorted: boolean;
empty: boolean;
};
pageNumber: number;
pageSize: number;
offset: number;
paged: boolean;
unpaged: boolean;
};
totalPages: number;
totalElements: number;
last: boolean;
first: boolean;
sort: {
sorted: boolean;
unsorted: boolean;
empty: boolean;
};
numberOfElements: number;
size: number;
number: number;
empty: boolean;
};
}
reqHospital
。api - home - index.ts
import request from "@/utils/request";
import type {
HospitalResponseData,
} from "./type";
enum API {
HOSPITAL_URL = "/hosp/hospital/",
}
export const reqHospital = (
page: number,
limit: number,
hostype = "",
districtCode = ""
) =>
request.get<any, HospitalResponseData>(
API.HOSPITAL_URL +
`${page}/${limit}?hostype=${hostype}&districtCode=${districtCode}`
);
page.index.vue
onMounted
- 页面加载完毕时回调的钩子。getHospitalInfo
- 获取已有的医院的数据<script setup lang="ts">
...
onMounted(() => {
getHospitalInfo();
});
const getHospitalInfo = async () => {
let result: HospitalResponseData = await reqHospital(
pageNo.value,
pageSize.value,
hosType.value,
districtCode.value
);
if (result.code == 200) {
hasHospitalArr.value = result.data.content;
total.value = result.data.totalElements;
}
};
...
</script>
<el-pagination
class="pagination"
v-model:current-page="pageNo"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30, 40]"
:background="true"
layout="prev, pager, next, jumper,->,sizes,total"
:total="total"
@current-change="currentChange"
@size-change="sizeChange"
/>
sizeChange
- 分页器下拉菜单发生变化的时候会触发currentChange
- 分页器页码发生变化时候回调const currentChange = () => {
getHospitalInfo();
};
const sizeChange = () => {
pageNo.value = 1;
getHospitalInfo();
};
<!-- 等级子组件 -->
<Level @getLevel="getLevel" />
<!--地区 -->
<Region @getRegion="getRegion" />
const getLevel = (level: string) => {
hosType.value = level;
getHospitalInfo();
};
const getRegion = (region: string) => {
districtCode.value = region;
getHospitalInfo();
};
const changeLevel = (level: string) => {
regionFlag.value = level;
$emit("getRegion", level);
};
let $emit = defineEmits(["getRegion"]);
Pinia
仓库hospital
路由的多个二级路由,需要获取点击医院的信息。所以使用Pinia
仓库进行数据缓存。把获得信息提供给多个二级路由使用,这样就可以不用每个页面再次获取医院信息。Pinia
仓库。store - modules - hospitalDetail.ts
注:使用pinia
前需要先安装pnpm i pinia
useDetailStore
为定义的全局数据。state
- 表示返回的数据。actions
- 触发的回调方法给state
中数据赋值。import { defineStore } from "pinia";
import { reqHospitalDetail, reqHospitalDeparment } from "@/api/hospital";
import type {
HospitalDetailData,
HospitalDetail,
} from "@/api/hospital/type";
import { DetailState } from "@/store/modules/interface";
const useDetailStore = defineStore("Detail", {
state: (): DetailState => {
return {
hospitalInfo: {} as HospitalDetail,
};
},
actions: {
async getHospital(hoscode: string) {
let result: HospitalDetailData = await reqHospitalDetail(hoscode);
if (result.code == 200) {
this.hospitalInfo = result.data;
}
},
},
getters: {},
});
export default useDetailStore;
hospital - index.vue
中使用 pinia
pinia
仓库发请求获取医院详情的数据,存储仓库当中。...
import { useRouter, useRoute } from "vue-router";
import { onMounted } from "vue";
import useDetailStore from "@/store/modules/hospitalDetail";
let detailStore = useDetailStore();
let $router = useRouter();
let $route = useRoute();
onMounted(() => {
detailStore.getHospital($route.query.hoscode as string);
});
hospital
二级路由拿到数据后获取展示即可。\pages\hospital\detail\index.vue
<script setup lang="ts">
import useDetailStore from "@/store/modules/hospitalDetail";
let hospitalStore = useDetailStore();
</script>
deparment
- 放置每一个医院的科室的数据deparmentInfo
- 用一个div代表:大科室与小科室 <div class="deparment">
<div class="leftNav">
<ul>
<li
@click="chaneIndex(index)"
:class="{ active: index == currentIndex }"
v-for="(deparment, index) in hospitalStore.deparmentArr"
:key="deparment.depcode"
>
{{ deparment.depname }}
li>
ul>
div>
<div class="deparmentInfo">
<div
class="showDeparment"
v-for="deparment in hospitalStore.deparmentArr"
:key="deparment.depcode"
>
<h1 class="cur" style="padding: 10px;">{{ deparment.depname }}h1>
<ul>
<li
@click="showLogin(item)"
v-for="item in deparment.children"
:key="item.depcode"
>
{{ item.depname }}
li>
ul>
div>
div>
div>
H1
标题)behavior: "smooth"
, 过渡动画效果block: "start"
, 滚动到位置 默认起始位置let currentIndex = ref<number>(0);
const chaneIndex = (index: number) => {
currentIndex.value = index;
let allH1 = document.querySelectorAll(".cur");
allH1[currentIndex.value].scrollIntoView({
behavior: "smooth",
block: "start",
});
};
&::-webkit-scrollbar { display: none;}
隐藏滚动条flex-direction: column;
浮动布局为列布局 .deparment {
width: 100%;
height: 500px;
display: flex;
margin-top: 20px;
.leftNav {
width: 80px;
height: 100%;
ul {
width: 100%;
height: 100%;
background: rgb(248, 248, 248);
display: flex;
flex-direction: column;
li {
flex: 1;
text-align: center;
color: #7f7f7f;
font-size: 14px;
line-height: 40px;
&.active {
border-left: 1px solid red;
color: red;
background: white;
}
}
}
}
.deparmentInfo {
flex: 1;
margin-left: 20px;
height: 100%;
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
.showDeparment {
h1 {
background-color: rgb(248, 248, 248);
color: #7f7f7f;
}
ul {
display: flex;
flex-wrap: wrap;
li {
color: #7f7f7f;
width: 33%;
line-height: 30px;
}
}
}
}
}
登录为全局组件,包含手机号登录,微信扫码登录表单验证等功能。
const validatorPhone = (_: any, value: any, callback: any) => {
const reg = /^1[3-9]\d{9}$/;
if (reg.test(value)) {
callback();
} else {
callback(new Error("请输入正确的号码格式"));
}
};
const validatorCode = (_: any, value: any, callback: any) => {
if (/^\d{6}$/.test(value)) {
callback();
} else {
callback(new Error("请输入正确的验证码格式"));
}
};
const rules = {
phone: [{ trigger: "blur", validator: validatorPhone }],
code: [{ trigger: "blur", validator: validatorCode }],
};
\components\countdown\index.vue
time
- 倒计时的事件watch
- 监听父组件传递过来props数据变化timer
- 开启定时器$emit('getFlag',false);
通知父组件倒计时模式结束let $emit = defineEmits(["getFlag"]);
接受父组件传递过来的props->flag
:用于控制计数器组件显示与隐藏的<template>
<div>
<span>获取验证码({{ time }}s)</span>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
let props = defineProps(["flag"]);
let time = ref<number>(5);
watch(
() => props.flag,
() => {
let timer = setInterval(() => {
time.value--;
if (time.value == 0) {
$emit("getFlag", false);
clearInterval(timer);
}
}, 1000);
},
{
immediate: true,
}
);
let $emit = defineEmits(["getFlag"]);
</script>
<Countdown v-if="flag" :flag="flag" @getFlag="getFlag" />
getFlag
- 计数器子组件绑定的自定义事件。当倒计时为零的时候,通知父组件倒计时组件隐藏getCode
- 获取验证码按钮的回调const getCode = async () => {
flag.value = true;
try {
await userStore.getCode(loginParams.phone);
loginParams.code = userStore.code;
} catch (error) {
ElMessage({
type: "error",
message: (error as Error).message,
});
}
};
const getFlag = (val: boolean) => {
flag.value = val;
};
Token
功能token
存户到pinia
。await userStore.userLogin(loginParams);
调用userStore
方法const login = async () => {
await form.value.validate();
console.log(form.value.validate());
try {
await userStore.userLogin(loginParams);
userStore.visiable = false;
let redirect = $route.query.redirect;
if (redirect) {
$router.push(redirect as string);
} else {
$router.push("/home");
}
} catch (error) {
ElMessage({
type: "error",
message: (error as Error).message,
});
}
};
SET_TOKEN(JSON.stringify(this.userInfo));
本地存储localStorage
持久化存储用户信息。 async userLogin(loginData: LoginData) {
let result: UserLoginResponseData = await reqUserLogin(loginData);
if (result.code == 200) {
this.userInfo = result.data;
SET_TOKEN(JSON.stringify(this.userInfo));
return 'ok';
} else {
return Promise.reject(new Error(result.message));
}
},
export const SET_TOKEN = (userInfo:string)=>{
localStorage.setItem('USERINFO',userInfo);
}
axios
中进行拦截请求头每次携带localStorage
中的token
信息config
:请求拦截器回调注入的对象(配置对象),配置对象的身上最终要的一件事情headers
属性request.interceptors.request.use((config) => {
let userStore = useUserStore();
if (userStore.userInfo.token) {
config.headers.token = userStore.userInfo.token;
}
return config;
});
注:这里显示了为什么要将登录注册为全局组件,因为项目分为两个部分,一个部分是不用登录可展示的部分,另一部分是登录之后的功能。挂号就是必须要登录之后才展示的功能。
\pages\hospital\register\register_step1.vue
文件中样式cur: item.workDate == workTime.workDate,
根据条件添加cur
属性,有cur
属性代表当前点击元素,从而进行放大。 <div
class="item"
@click="changeTime(item)"
:class="{
active: item.status == -1 || item.availableNumber == -1,
cur: item.workDate == workTime.workDate,
}"
v-for="item in workData.bookingScheduleList"
:key="item"
>
transform: scale(1.1);
动画放大了1.1倍 .container {
width: 100%;
display: flex;
margin: 30px 0px;
.item {
flex: 1;
width: 100%;
border: 1px solid skyblue;
margin: 0px 5px;
color: #7f7f7f;
transition: all 0.5s;
&.active {
border: 1px solid #ccc;
.top1 {
background: #ccc;
}
}
&.cur {
transform: scale(1.1);
}
.top1 {
background: #e8f2ff;
height: 30px;
width: 100%;
text-align: center;
line-height: 30px;
}
.bottom {
width: 100%;
height: 60px;
text-align: center;
line-height: 60px;
}
}
}
components\visitor\visitor.vue
transition
标签并声明名称为confirm
<template>
<div class="visitor">
...
<div class="bottom">
<p>证件类型:{{ user.param?.certificatesTypeString }}p>
<p>证件号码:{{ user.certificatesNo }}p>
<p>用户性别:{{ user.sex == 0 ? "女生" : "男士" }}p>
<p>出生日期:{{ user.birthdate }}p>
<p>手机号码:{{ user.phone }}p>
<p>婚姻状况:{{ user.isMarry == 0 ? "未婚" : "已婚" }}p>
<p>详细地址:{{ user.address }}p>
<transition name="confirm">
<div class="confirm" v-if="index == currentIndex">已选择div>
transition>
div>
div>
template>
position: absolute;
绝对定位border-radius: 50%;
圆角50%
opacity: 0.5;
透明度0.5
transform: rotate(35deg);
印章旋转角度.confirm-enter-from,.confirm.enter-active, .confirm-enter-to
出入动画,持续动画时间。 .bottom {
position: relative;
padding: 0px 10px;
p {
line-height: 40px;
}
.confirm {
position: absolute;
width: 200px;
height: 200px;
color: red;
border-radius: 50%;
border: 1px dashed red;
text-align: center;
line-height: 200px;
left: 15%;
top: 15%;
opacity: 0.5;
transform: rotate(35deg);
font-weight: 900;
}
.confirm-enter-from {
transform: scale(1);
}
.confirm.enter-active {
transform: all 0.3s;
}
.confirm-enter-to {
transform: scale(1.2);
}
}
pages\user\order\allOrder\index.vue
pages\user\order\detail\index.vue
pages\user\certification\index.vue
中使用el-upload
控件/api/oss/file/fileUpload?fileHost=userAuah&userId=${userInfo.id}
” 该属性表示选择图片后上传的后端服务器地址。 <el-upload
ref="upload"
list-type="picture-card"
:limit="1"
:on-exceed="exceedhandler"
:on-success="successhandler"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:action="`/api/oss/file/fileUpload?fileHost=userAuah&userId=${userInfo.id}`"
>
<img
style="width: 100%; height: 100%"
src="@\assets\images\auth_example.png"
alt=""
/>
el-upload>
exceedhandler
- 超出数量的钩子successhandler
- 图品上传成功的钩子handlePictureCardPreview
- 照片墙预览的钩子handleRemove
- 删除图片的钩子注:预览图片的地址为后端上传成功后返回的地址。
const exceedhandler = () => {
ElMessage({
type: "error",
message: "图片只能上传一张图片",
});
};
const successhandler = (response: any) => {
form.value.clearValidate("certificatesUrl");
params.certificatesUrl = response.data;
console.log(params.certificatesUrl);
};
const handlePictureCardPreview = () => {
//触发预览的钩子的时候,对话框显示
dialogVisible.value = true;
};
const handleRemove = () => {
params.certificatesUrl = "";
};
el-cascader
级联选择器 <el-cascader :props="props" v-model="userParams.addressSelected" style="width: 100%" />
ts
中引入级联选择器配置lazyLoad(node: any, resolve: any)
- 加载级联选择器数据方法resolve(showData);
- 注入组件需要展示的数据id: item.value,
- 每次回传id
查询下一级的选择列表。import type { CascaderProps } from "element-plus";
const props: CascaderProps = {
lazy: true,
async lazyLoad(node: any, resolve: any) {
let result: any = await reqCity(node.data.id || "86");
let showData = result.data.map((item: any) => {
return {
id: item.value,
label: item.name,
value: item.value,
leaf: !item.hasChildren,
};
});
resolve(showData);
},
};
Nprogress
加载进度条let whiteList
- 存储用户未登录可以访问路由得路径router.beforeEach((to, from, next)
- 前置守卫Nprogress.start();
访问路由组件的之前,进度条开始动document.title = 大姚挂号平台-${to.meta.title};
动态设置网页左上角的标题 let token = userStore.userInfo.token;
判断是否有token
router.afterEach((to, from)
- 后置路由Nprogress.done();
- 访问路由组件成功,进度条消息import router from "./router";
import Nprogress from 'nprogress';
import useUserStore from '@/store/modules/user';
import pinia from '@/store'
let userStore = useUserStore(pinia);
import "nprogress/nprogress.css"
let whiteList = ["/home", '/hospital/register', '/hospital/detail', '/hospital/notice', '/hospital/close', '/hospital/search'];
router.beforeEach((to, from, next) => {
Nprogress.start();
document.title = `大姚挂号平台-${to.meta.title}`;
let token = userStore.userInfo.token;
if (token) {
next();
} else {
if (whiteList.includes(to.path)) {
next();
} else {
userStore.visiable = true;
next({ path: '/home', query: { redirect: to.fullPath } })
}
}
});
router.afterEach((to, from) => {
Nprogress.done();
})
注:后端架构在《浅谈一下个人基于IRIS后端业务开发框架的理解》此博文中详细阐述,所以不再赘述。
BRP.BS.Account
BRP.BS.BookingSchedule
BRP.BS.Doctor
BRP.BS.Doctor
BRP.BS.Patient
BRP.CT.Region
BRP.CT.OrderStatus
BRP.CT.Level
BRP.CT.Hospital
BRP.CT.Department
BRP.CT.City
BRP.CT.CertificatesType
BRP.CT.Config
所有表的关键字和父类如下:
%JSON.Adaptor
- 用于导入导出json
数据BRP.COM.Map
- ORM
映射类Class BRP.CT.Hospital Extends (%Persistent, %JSON.Adaptor, BRP.COM.Map) [ ClassType = persistent, Inheritance = left, ProcedureBlock, SqlRowIdName = id ]
注:字段就不一一列举了,可下源代码自行导入。
BRP.COM.Init
w ##class(BRP.COM.Init).InitRegion("/brp/")
即可。Class BRP.COM.Init Extends %RegisteredObject
{
/// w ##class(BRP.COM.Init).InitRegion("/brp/")
ClassMethod Init(path)
{
ts
d ..InitRegion(path _ "region.txt")
d ..InitLevel(path _ "level.txt")
d ..InitHospital(path _ "hospital.txt")
d ..InitDepartment(path _ "department.txt")
d ..InitBookingSchedule()
d ..InitDocotr(path _ "doctor.txt")
d ..InitPatient(path _ "patient.txt")
d ..InitOrderStatus()
d ..InitCertificatesType(path _ "certificatesType.txt")
d ..InitCity(path _ "city.txt")
d ..InitCitySecond()
b ;all
tc
q $$$OK
}
/// 初始化区域数据
/// w ##class(BRP.COM.Init).InitRegion("/brp/region.txt")
ClassMethod InitRegion(path)
{
ts
s json = {}.%FromJSONFile(path)
s iter = json.%GetIterator()
while (iter.%GetNext(.key, .val)){
s id = ##class(Util.GenerateClassUtils).SaveJson2Entity(val, "BRP.CT.Region")
w "生成id:" _ id,!
}
b ;22
tc
q $$$OK
}
...
}
BRP.COM.REST.Api
CONTENTTYPE
- 接口返回数据格式CHARSET
- 编码类型REST
接口有GET,POST,PUT,DELETE
请求方法。$g(%request.Data("districtCode", 1))
- 为路径中的Query
参数。s content = %request.Content
- 为获取POST
请求参数内容。Class BRP.COM.REST.Api Extends %CSP.REST
{
Parameter CONTENTTYPE = "application/json";
Parameter CHARSET = "utf-8";
/// http://119.3.235.244:52773/api/hosp/hospital/1/10
/// w ##class(BRP.COM.REST.Api).QueryHospitalByPage(1,10)
ClassMethod QueryHospitalByPage(page, limit) As %Status
{
s pDistrictCode = $g(%request.Data("districtCode", 1))
s pHostype = $g(%request.Data("hostype",1 ))
s json = ##class(BRP.COM.SafeRun).Execute("BRP.API.Hospital", "Query", pHostype, pDistrictCode)
s json = ##class(BRP.COM.SafeRun).ReturnResultPage(json, page, limit)
w json
q $$$OK
}
/// http://119.3.235.244:52773/api/cmn/dict/findByDictCode/HosType
/// w ##class(BRP.COM.REST.Api).QueryRegion("HosType")
ClassMethod QueryRegion(dictCode) As %Status
{
if (dictCode = "HosType") {
s json = ##class(BRP.COM.SafeRun).Execute("BRP.API.Level", "Query")
} elseif (dictCode = "CertificatesType") {
s json = ##class(BRP.COM.SafeRun).Execute("BRP.API.CertificatesType", "Query")
} else {
s json = ##class(BRP.COM.SafeRun).Execute("BRP.API.Region", "Query")
}
w json
q $$$OK
}
...
/// Url顺序区别参数多的放到下方,否则执行不到对应的路径
XData UrlMap
{
<Routes>
<Route Url="/cmn/dict/findByDictCode/:dictCode" Method="GET" Call="QueryRegion"/>
<Route Url="/hosp/hospital/department/:hoscode" Method="GET" Call="QueryDepartment"/>
<Route Url="/hosp/hospital/getSchedule/:scheduleId" Method="GET" Call="GetDoctorDataById"/>
<Route Url="/hosp/hospital/findByHosname/:hosname" Method="GET" Call="QueryHospital"/>
<Route Url="/hosp/hospital/:page/:limit" Method="GET" Call="QueryHospitalByPage"/>
<Route Url="/hosp/hospital/:hoscode" Method="GET" Call="GetHospitalData"/>
<Route Url="/sms/send/:phone" Method="GET" Call="GetCaptcha"/>
<Route Url="/user/login" Method="POST" Call="Login"/>
<Route Url="/hosp/hospital/auth/getBookingScheduleRule/:page/:limit/:hoscode/:depcode" Method="GET" Call="QueryBookingSchedule"/>
<Route Url="/hosp/hospital/auth/findScheduleList/:hoscode/:depcode/:workDate" Method="GET" Call="QueryDoctor"/>
<Route Url="/user/patient/auth/findAll" Method="GET" Call="QueryPatient"/>
<Route Url="/order/orderInfo/auth/submitOrder/:hoscode/:scheduleId/:patientId" Method="POST" Call="SubmitOrder"/>
<Route Url="/order/orderInfo/auth/getOrderInfo/:id" Method="GET" Call="GetOrderDataById"/>
<Route Url="/order/orderInfo/auth/cancelOrder/:id" Method="GET" Call="CancelOrder"/>
<Route Url="/order/orderInfo/auth/:page/:limit" Method="GET" Call="QueryOrder"/>
<Route Url="/order/orderInfo/auth/getStatusList" Method="GET" Call="QueryOrderStatus"/>
<Route Url="/user/patient/auth/save" Method="POST" Call="SavePatient"/>
<Route Url="/user/patient/auth/update" Method="PUT" Call="SavePatient"/>
<Route Url="/user/patient/auth/remove/:id" Method="DELETE" Call="DeletePatient"/>
<Route Url="/cmn/dict/findByParentId/:parentId" Method="GET" Call="QueryCity"/>
<Route Url="/user/auth/getUserInfo" Method="GET" Call="GetUserInfo"/>
<Route Url="/oss/file/fileUpload" Method="POST" Call="UploadFile"/>
<Route Url="/user/auth/userAuah" Method="POST" Call="UserAuah"/>
</Routes>
}
}
BRP.COM.SafeRun
注:统一入口,统一错误处理在《通用的异常处理程序机制与处理返回值方案》此博文中详细阐述,所以不再赘述。
Class BRP.COM.SafeRun Extends (%RegisteredObject, BRP.COM.Base)
{
/// desc:统一入口网关,方便统一操作日志,错误等。
ClassMethod Gateway()
{
s pClassName = $g(%request.Data("ClassName", 1))
s pMethodName = $g(%request.Data("MethodName",1 ))
s params = ..GetMethodParams(pClassName, pMethodName)
s ^yx("Gateway") = $lb(pClassName,pMethodName,params)
q ..Execute(pClassName, pMethodName, params)
}
/// desc:所有程序中错误军抛出异常,统一处理
/// w ##class(IMP.Common.SafeRun).Execute("IMP.Perms.Biz", "QueryRole")
ClassMethod Execute(pClassName, pMethodName, pParams...)
{
s $ecode = ""
try{
d ..GetInfo()
#; 判断方法是否有参数,没有参数不用串pParams,否则报错。
if (##class(BRP.UTIL.Class).GetParamsList(pClassName, pMethodName) = "")
{
s result = $classmethod(pClassName, pMethodName)
} else {
s result = $classmethod(pClassName, pMethodName, pParams...)
}
} catch e {
lock
tro:($tl > 0)
if (e.%IsA("BRP.COM.Exception.SessionException")){
ret ..Session2Json()
}
s data = e.Data
s:$lv(data) data = $$Format^%qcr(data, 1)
s msg = e.Name _ e.Location _ " *" _ data _ $ze
s ret = $$LOG^%ETN(msg)
#; SQL异常
if (e.%IsA("BRP.COM.Exception.SqlException")){
s msg = e.AsSystemError()
ret ..Failure2Json(msg)
} elseif (e.%IsA("%Exception.SystemException")){ // 系统异常
ret ..Failure2Json($ze )
} elseif (e.%IsA("BRP.COM.Exception.ErrorException")){ // 通用错误异常
ret ..Failure2Json(msg)
} elseif (e.%IsA("%Exception.StatusException")){ // Status错误异常
s msg = e.DisplayString()
ret ..Failure2Json(msg)
} elseif (e.%IsA("BRP.COM.Exception.WarningException")){ // 警告信息
s msg = e.AsSystemError()
ret ..Warning2Json(msg)
} else { // 其他兜底异常
if (msg = "")
{
s msg = $ze
}
ret ..Failure2Json(msg)
}
}
ret ..ReturnResult(result)
}
ClassMethod HandleToken(token)
{
throw:(token = "") $$$WarningException("token异常:", "请先登录")
s ret = ##class(Util.Jwt).VerifyJwt(token)
q ret
}
}
ORM
映射的使用注:ORM
映射的使用在《只需要改造一下实体类,以后再也不用写SQL了》此博文中详细阐述,所以不再赘述。
以查询区域Rest
接口演示后端框架与ORM
的使用
BRP.BL.QUERY.Region
中编写查询区域数组的方法。BRP.CT.Region
- 为区域实体表。Query
方法为封装的ORM
映射返回的列表方法可直接返回数组。Class BRP.BL.QUERY.Region Extends %RegisteredObject
{
ClassMethod Query()
{
q ##class(BRP.CT.Region).Query()
}
}
BRP>zw ##class(BRP.BL.QUERY.Region).Query()
[{"id":17,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"东城区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110101"},{"id":18,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"西城区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110102"},{"id":19,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"朝阳区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110105"},{"id":20,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"丰台区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110106"},{"id":21,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"石景山区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110107"},{"id":22,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"海淀区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110108"},{"id":23,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"门头沟区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110109"},{"id":24,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"房山区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110111"},{"id":25,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"通州区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110112"},{"id":26,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"顺义区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110113"},{"id":27,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"昌平区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110114"},{"id":28,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"大兴区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110115"},{"id":29,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"怀柔区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110116"},{"id":30,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"平谷区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110117"},{"id":31,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"密云区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110118"},{"id":32,"createTime":"2020-06-23 15:48:57","dictCode":null,"hasChildren":false,"isDeleted":0,"name":"延庆区","param":null,"parentId":110100,"updateTime":"2020-06-23 15:52:57","value":"110119"}] ; <DYNAMIC ARRAY>
BRP.API.Region
继承BRP.BL.QUERY.Region
并重写Query()
并返回。##super()
- 调用父同名方法Class BRP.API.Region Extends BRP.BL.QUERY.Region
{
/// zw ##class(BRP.API.Region).Query()
ClassMethod Query()
{
q ##super()
}
}
s json = ##class(BRP.COM.SafeRun).Execute("BRP.API.Level", "Query")
Rest
中定义方法ClassMethod QueryRegion(dictCode) As %Status
{
s json = ##class(BRP.COM.SafeRun).Execute("BRP.API.Region", "Query")
w json
q $$$OK
}
XData
路由中定义路径与方法,请求方式。XData UrlMap
{
<Routes>
<Route Url="/cmn/dict/findByDictCode/:dictCode" Method="GET" Call="QueryRegion"/>
</Routes>
}
http://119.3.235.244:52773/api/cmn/dict/findByDictCode/1
JWT
功能注:JWT功能在《基于M实现的JWT解决方案》此博文中详细阐述,所以不再赘述。
JWT
并将JWT
保存到用户表或临时Global
,用于后续对比。ClassMethod Login(phone = "", pCaptcha = "")
{
s captchaData = ^BRP("Login", "GetCaptcha", phone)
s captcha = $lg(captchaData, 1)
throw:(captcha '= pCaptcha) $$$LoginException("验证码错误")
s id = $o(^BRP.BS.AccountI("Phone", phone, ""))
if id = "" {
s obj = ##class(BRP.BS.Account).%New()
} else {
s obj = ##class(BRP.BS.Account).%OpenId(id)
}
s jwt = ##class(Util.Jwt).GenerateJwt()
s obj.name = phone
s obj.phone = phone
s obj.token = jwt
s sc = obj.%Save()
throw:($$$ISERR(sc)) $$$LoginException($system.Status.GetErrorText(sc))
s obj = {}
s obj.name = phone
s obj.token = jwt
q obj
}
TOKEN
,后端在必要的接口中进行判断。s token = $g(%request.CgiEnvs("HTTP_TOKEN"))
获取头信息中的TOKEN
。HandleToken
为验证token
是否合法。ClassMethod QueryDoctor(hoscode, depcode, workDate) As %Status
{
s token = $g(%request.CgiEnvs("HTTP_TOKEN"))
s json = ##class(BRP.COM.SafeRun).Execute("BRP.COM.SafeRun", "HandleToken", token)
if ({}.%FromJSON(json).code = 0) {
w json
} else{
s json = ##class(BRP.COM.SafeRun).Execute("BRP.API.Doctor", "Query", hoscode, depcode, workDate)
w json
}
q $$$OK
}
TOKEN
会提示请先登录。TOKEN
并成功数据返回正常数据。HTTP
上传图片功能。REST
上传文件接口与路由。s binaryStream = $g(%request.MimeData("file", 1))
获取前端上传的文件流<Route Url="/oss/file/fileUpload" Method="POST" Call="UploadFile"/>
ClassMethod UploadFile() As %Status
{
s binaryStream = $g(%request.MimeData("file", 1))
s userId = $g(%request.Data("userId",1 ))
s json = ##class(BRP.COM.SafeRun).Execute("BRP.API.Amount", "UploadFile", userId , binaryStream)
w json
q $$$OK
}
http
图片路径。ClassMethod UploadFile(userId, binaryStream)
{
s del = "\"
s path = "C:\InterSystems\IRISHealth2023\CSP\brp\pic"
if $$$isUNIX {
s del = "/"
s path = "/isc/iris/csp/brp/pic"
}
s bool = ##class(%File).CreateDirectoryChain(path, .return)
s fileName = $zstrip($zdt($now(), 8, , 3), "*E'N") _ ".png"
s fullFileName = path _ del _ fileName
#dim fileBinaryStream as %Stream.FileBinary = ##class(%Stream.FileBinary).%New()
s sc = fileBinaryStream.LinkToFile(fullFileName)
$$$ThrowOnError(sc)
s sc = fileBinaryStream.CopyFromAndSave(binaryStream)
$$$ThrowOnError(sc)
s obj = ##class(BRP.BS.Account).%OpenId(userId)
s obj.certificatesPhysicalUrl = fullFileName
s sc = obj.%Save()
$$$ThrowOnError(sc)
q "http://119.3.235.244:52773/csp/brp/pic/" _ fileName
}
2
核4G
,1
个月免费体验,点击立即体验CentOS7.9
,点击立即购买HECS
云服务器。Xshell
访问云服务器了。Xshell
登录了。Linux
,Centos
系统中安装数据库ISC
官网点击下载连接,此时会让你登录帐号,如果没有请先注册。3.选择IRIS
,并选择操作系统与版本,最后点击下载。
/isc
cd /isc
tar -zxvf IRISHealth-2021.1.3.389.0.22951-lnxrhx64.tar.gz
cd
到解压的目录cd /isc/IRISHealth-2021.1.3.389.0.22951-lnxrhx64
./irisinstall
,最后会提示安装完成即可。51773,52773,1972,80,8088,2188
。studio
访问云库与云portal
了。portal
>系统 > 配置 > 本地数据库,点击创建数据库BRP-DATA
数据库用于保存Global
与BRP-SRC
数据库用于保存例程。下一步到完成即可。BRP
Global
数据库为:BRP-DATA
Routine
数据库为:BRP-SRC
Web
应用程序,点击新建应用程序填写如下:/api
BRP
REST
BRP.COM.REST.Api
rest
接口可通过http
访问。Application
中的/api
路径会自动追加到定义的路由路径中。HTTP
接口返回403
,是因为UnknownUser
权限不足,重新分配权限。BRP-CF
,BRP-BS
,BRP-CT
数据库。BRP-CF
:配置数据库BRP-CT
:字典数据库BRP-BS
:业务数据库BRP
命名空间,选择Global
映射。增加如下映射。BRP.BS*
开头的Global
映射到BRP-BS
库中BRP.CF*
开头的Global
映射到BRP-BS
库中BRP.CT*
开头的Global
映射到BRP-BS
库中Global
中,验证三库分离是否成功。Databse
查看对应的Globa
l数据是否值,并且Global
节点是否映射正确。ISCAgent
服务。systemctl start ISCAgent
systemctl status ISCAgent
systemctl enable ISCAgent
[root@hecs-210841 ~]# systemctl start ISCAgent
[root@hecs-210841 ~]# systemctl status ISCAgent
● ISCAgent.service - InterSystems Agent
Loaded: loaded (/etc/systemd/system/ISCAgent.service; enabled; vendor preset: disabled)
Active: active (running) since Mon 2023-12-25 20:40:46 CST; 13h ago
Main PID: 18214 (ISCAgent)
CGroup: /system.slice/ISCAgent.service
├─18214 /usr/local/etc/irissys/ISCAgent
├─18215 /usr/local/etc/irissys/ISCAgent
├─20695 /usr/local/etc/irissys/ISCAgent
└─20697 /usr/local/etc/irissys/ISCAgent
[root@hecs-210841 ~]# systemctl enable ISCAgent
portal
,配置 - 镜像设置- 创建镜像才菜单可以使用了,在DB华为云上创建镜像。244DB
XXX.XXX.XXX.XXX
2188
XXX.XXX.XXX.XXX
MIRROR
上,点击 系统 > 配置 > 将镜像加入为故障转移。244DB
IRIS
DB
上查看镜像监视器,发现镜像成功DB
上执行如下命令进行数据库备份,然后在MIRROR
上进行数据恢复。%SYS>d ^BACKUP
1) Backup
2) Restore ALL
3) Restore Selected or Renamed Directories
4) Edit/Display List of Directories for Backups
5) Abort Backup
6) Display Backup volume information
7) Monitor progress of backup or restore
Option? 1
*** The time is: 2023-12-26 10:14:36 ***
InterSystems IRIS Backup Utility
--------------------------
What kind of backup:
1. Full backup of all in-use blocks
2. Incremental since last backup
3. Cumulative incremental since last full backup
4. Exit the backup program
1 => y
?? Type the number of the function you want
What kind of backup:
1. Full backup of all in-use blocks
2. Incremental since last backup
3. Cumulative incremental since last full backup
4. Exit the backup program
1 => 1
Specify output device (type STOP to exit)
Device: /isc/FullDBList_user.cbk => /isc/setmirror.cbk =>
Backing up to device: /isc/FullDBList_user.cbk => /isc/setmirror.cbk
Description: backupdb
Backing up the following directories:
/isc/iris/mgr/
/isc/iris/mgr/HSCUSTOM/
/isc/iris/mgr/brpbs/
/isc/iris/mgr/brpcf/
/isc/iris/mgr/brpct/
/isc/iris/mgr/brpdata/
/isc/iris/mgr/brpsrc/
/isc/iris/mgr/hslib/
/isc/iris/mgr/hssys/
/isc/iris/mgr/irisaudit/
/isc/iris/mgr/user/
Start the Backup (y/n)? => y
MIRROR
服务器上。scp
本地cbk
路径,要发送的服务器地址。MIRROR
服务器密码,等待传送数据即可。scp /isc/setmirror.cbk root@123.57.246.161:/isc
Enter passphrase for key '/root/.ssh/id_rsa':
root@123.57.246.161's password:
setmirror.cbk
MIRROR
服务器上执行如下命令进行数据库恢复。
%SYS>do ^DBREST
Cache DBREST Utility
Restore database directories from a backup archive
Restore: 1. All directories
2. Selected and/or renamed directories
3. Display backup volume information
4. Exit the restore program
1 => 2
Do you want to set switch 10 so that other processes will be
prevented from running during the restore? Yes => yes
Specify input file for volume 1 of backup 1
(Type STOP to exit)
Device: /isc/setmirror.cbk
This backup volume was created by:
IRIS for UNIX (Red Hat Enterprise Linux for x86-64) 2021.1.3
The volume label contains:
Volume number 1
Volume backup DEC 25 2023 09:43PM Full
Previous backup
Last FULL backup
Description /isc/setmirror.cbk
Buffer Count 0
Mirror name 244DB
Failover Member HECS-210841/IRIS
Is this the backup you want to start restoring? Yes => yes
This backup was made on the other mirror member.
Limit restore to mirrored databases? no
For each database included in the backup file, you can:
-- press RETURN to restore it to its original directory;
-- type X, then press RETURN to skip it and not restore it at all.
-- type a different directory name. It will be restored to the directory
you specify. (If you specify a directory that already contains a
database, the data it contains will be lost).
/isc/iris/mgr/ =>
/isc/iris/mgr/HSCUSTOM/ =>
/isc/iris/mgr/brpbs/ =>
/isc/iris/mgr/brpcf/ =>
/isc/iris/mgr/brpct/ =>
/isc/iris/mgr/brpdata/ =>
/isc/iris/mgr/brpsrc/ =>
/isc/iris/mgr/hslib/ =>
/isc/iris/mgr/hssys/ =>
/isc/iris/mgr/irisaudit/ =>
/isc/iris/mgr/user/ =>
Do you want to change this list of directories? No =>
Restore will overwrite the data in the old database. Confirm Restore? No => yes
***Restoring /isc/iris/mgr/ at 09:36:09
13107 blocks restored in 0.4 seconds for this pass, 13107 total restored.
***Restoring /isc/iris/mgr/HSCUSTOM/ at 09:36:10
1397 blocks restored in 0.1 seconds for this pass, 1397 total restored.
...
***Restoring /isc/iris/mgr/user/ at 09:36:45
1 blocks restored in 0.0 seconds for this pass, 79 total restored.
Specify input file for volume 1 of backup following DEC 25 2023 09:43PM
(Type STOP to exit)
Device:
Do you have any more backups to restore? Yes => no
Mounting /isc/iris/mgr/
/isc/iris/mgr/ ... (Mounted)
Mounting /isc/iris/mgr/HSCUSTOM/
/isc/iris/mgr/HSCUSTOM/ ... (Mounted)
...
Mounting /isc/iris/mgr/user/
/isc/iris/mgr/user/ ... (Mounted)
Restoring a directory restores the globals in it only up to the
date of the backup. If you have been journaling, you can apply
journal entries to restore any changes that have been made in the
globals since the backup was made.
What journal entries do you wish to apply?
1. All entries for the directories that you restored
2. All entries for all directories
3. Selected directories and globals
4. No entries
Apply: 1 => 1
We know something about where journaling was at the time of the backup:
0: offset 198000 in /isc/iris/mgr/journal/MIRROR-244DB-20231225.003
Are journal files created by this IRIS instance and located in their original
paths? (Uses journal.log to locate journals)? enter Yes or No, please
Are journal files created by this IRIS instance and located in their original
paths? (Uses journal.log to locate journals)? yes
The earliest journal entry since the backup was made is at
offset 198000 in /isc/iris/mgr/journal/MIRROR-244DB-20231225.003
Do you want to start from that location? Yes => Yes
/isc/iris/mgr/journal/MIRROR-244DB-20231225.003 is NOT in journal history log /isc/iris/mgr/journal.log
You might have specified a wrong journal history log
[Not restored]
%SYS>
MIRROR
数据Global
恢复成功。MIRROR
的数据库,添加即可。NGINX
nginx
包tar -zxvf nginx-1.24.0.tar.gz
./configurem
配置命令,发现有很多not found
yum install gcc-c++
yum install -y pcre pcre-devel
yum install -y zlib zlib-devel
yum install -y openssl openssl-devel
make && make install
/usr/local/nginx
查看文件是否存在conf
、sbin
、html
文件夹,若存在则安装成功brp-web-app
项目的termina
中执行打包命令pnpm run build
。会生成的打包文件dist
。dist
文件放入到云服务器nginx
的html
目录中./nginx
启动。若/usr/local/nginx/logs
中error.log无报错则表示启动成功。nginx
后发现无法访问后台接口提示404
或CORS
跨越错误。需要做出如下修改:vite.config.ts
中增加如下配置:/api
表示路径中包含此路径代表是需要代理的请求。import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
...
server: {
proxy: {
"/api": {
target: "http://119.3.235.244:52773/",
changeOrigin: true,
},
},
},
});
\utils\request.ts
中增加如下配置:/api
路径。const request = axios.create({
baseURL: "/api",
timeout: 5000,
});
ngnix
的conf
路径下nginx.conf
做出如下修改:location /api
表示在ngnix
中为此路径字段替换为http://119.3.235.244:52773
进行转发。注:此处的/api
与http://119.3.235.244:52773
后边均不带/
,写成/api/
与http://119.3.235.244:52773/
则代理失败。需要与前端文件的vite
配置路径一一对应。
server {
listen 80;
server_name localhost;
client_header_buffer_size 1M;
large_client_header_buffers 4 1M;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
try_files $uri $uri/ /index.html last;
index index.html index.htm;
client_max_body_size 100M;
}
location /api {
proxy_pass http://119.3.235.244:52773;
break;
}
}
./nginx -s quit && ./nginx*
,查看跨越问题是否解决。Git
进行版本控制注:Git
详细使用可以查看《Git
使用大全》
Git
仓库Github
git
上生成token
Github上
创建仓库brp-app-web
,选择公开仓库。Push
到GitHub
远程仓库。Token
。