Browse Source

Merge branch 'master' of http://git.fmode.cn:3000/bin/edu-textbook

warrior 8 months ago
parent
commit
34dc94bade

+ 2 - 0
.gitignore

@@ -1,5 +1,7 @@
 # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
 
+opt/
+
 # Compiled output
 /dist
 /tmp

+ 12 - 0
README.md

@@ -105,6 +105,18 @@ https://api-explorer.authing.cn/?source=Authing%20%E7%94%A8%E6%88%B7%E8%AE%A4%E8
 
 测试用户池密钥:491670e7ef2fd553ce6d0668a1bb87f3
 
+
+# 开发人员协作
+- 高教社运维负责
+    - 4966367 wuhuaide 1649663385 
+    - 吴怀德  
+    - wuhuaide (867947897@qq.com)
+- Authing项目负责
+    - 韩磊-leohan83
+    - 韩磊   我自己
+    - leohan83 (hanlei@authing.com)
+
+
 # 国家级教材遴选系统——前端项目 edu-textbook
 
 This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.0.4.

+ 2 - 1
deploy-www.sh

@@ -11,6 +11,7 @@ TEMP_WWW=/opt/edu-textbook-www
 ng build textbook
 ssh $DEV_HOST "mkdir -p $TEMP_WWW"
 tar zcvf dist/edu-textbook-www.tar.gz dist/textbook/browser/
+cp dist/edu-textbook-www.tar.gz docker-front/opt/ # docker 目录
 scp dist/edu-textbook-www.tar.gz $DEV_HOST:$TEMP_WWW/
 ssh $DEV_HOST "scp -r $TEMP_WWW/edu-textbook-www.tar.gz $PROD_HOST:/var/www/"
-ssh $DEV_HOST "ssh $PROD_HOST \"cd /var/www/ && tar zxvf edu-textbook-www.tar.gz && cp -rf dist/textbook/browser/* edu-textbook/ \"" # 仅安装prod
+ssh $DEV_HOST "ssh $PROD_HOST \"cd /var/www/ && tar zxvf edu-textbook-www.tar.gz && cp edu-textbook-www.tar.gz edu-textbook/ && cp -rf dist/textbook/browser/* edu-textbook/ \"" # 仅安装prod

+ 1 - 0
docker-front

@@ -0,0 +1 @@
+Subproject commit c6edfa4c6e50ff5f078de4c9cbe976bd233d3462

+ 0 - 0
docker/.gitignore


+ 64 - 0
docker/Dockerfile

@@ -0,0 +1,64 @@
+FROM debian:bullseye-slim
+MAINTAINER FmodeInc "support@fmode.cn"
+
+######################################## APT Repos to 163
+# RUN apt-get update
+
+ADD sources.list /etc/apt/
+
+RUN apt-get update
+
+######################################## NODE
+
+RUN groupadd --gid 1000 node \
+  && useradd --uid 1000 --gid node --shell /bin/bash --create-home node
+
+RUN apt-get update --fix-missing
+
+RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \
+    && case "${dpkgArch##*-}" in \
+      amd64) ARCH='x64';; \
+      ppc64el) ARCH='ppc64le';; \
+      s390x) ARCH='s390x';; \
+      arm64) ARCH='arm64';; \
+      armhf) ARCH='armv7l';; \
+      i386) ARCH='x86';; \
+      *) echo "unsupported architecture"; exit 1 ;; \
+    esac \
+    && set -ex \
+    # libatomic1 for arm
+    && apt-get update && apt-get install -y ca-certificates apt-utils curl wget gnupg dirmngr xz-utils libatomic1 --no-install-recommends \
+    && rm -rf /var/lib/apt/lists/* \
+    && curl -fsSLO --compressed "https://npmmirror.com/mirrors/node/v18.19.1/node-v18.19.1-linux-$ARCH.tar.xz" \
+    && curl -fsSLO --compressed "https://npmmirror.com/mirrors/node/v18.19.1/SHASUMS256.txt.asc" \
+    && tar -xJf "node-v18.19.1-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \
+    && rm "node-v18.19.1-linux-$ARCH.tar.xz" \
+    && ln -s /usr/local/bin/node /usr/local/bin/nodejs
+
+
+######################################## Nginx
+# curl http检测
+# procps 进程管理
+RUN apt-get update &&apt-get install -y nginx procps && apt-get install -y nginx curl --no-install-recommends \
+  && rm -rf /etc/nginx/sites-enabled/*  && service nginx restart
+
+######################################## Copy Latest System
+COPY ./opt/ /opt/
+
+RUN mkdir -p /opt/edu-textbook-server/ && cd /opt/ && tar zxvf server.tar.gz && mv server/* edu-textbook-server/ && mkdir -p /etc/nginx/sites-enabled/ && cp -rf /opt/edu-textbook-server/config/nginx-server.conf /etc/nginx/sites-enabled/ && service nginx restart
+
+RUN ls /opt/ && chown -R root:root /opt/*
+
+
+######################################## PM2 && server node_modules
+RUN npm config set registry https://registry.npmmirror.com/ && npm i -g npm@10.8.1
+RUN cd /opt/edu-textbook-server/ && npm i --omit dev  --no-warn
+
+CMD ["service","nginx","restart"]
+
+# DEFAULT WEB
+EXPOSE 80
+# DEFAULT SSL
+EXPOSE 443
+
+ENTRYPOINT [ "node","/opt/edu-textbook-server/server.js" ]

+ 40 - 0
docker/README.md

@@ -0,0 +1,40 @@
+# 十四五本科教材部署项目
+
+# 目录资源
+- /opt/ 软件包
+    - edu-textbook-www.tar.gz 前端项目
+    - server.tar.gz 后端项目
+- sources.list 国内源
+- Dockerfile 容器编制脚本
+
+# 环境变量
+``` bash
+# 环境变量
+DATABASE_DBNAME # 链接字符串 postgres://xxxxxxx:5432/textbook
+ALI_OSS_BUCKET # 阿里云OSS bucket
+ALI_OSS_ACCESS_KEY_ID # 阿里云OSS ak
+ALI_OSS_ACCESS_KEY_SECRE # 阿里云OSS sk
+```
+
+# 常用指令
+
+``` bash
+# 容器构建
+sudo docker build --tag fmode:edu-textbook-1.0.0 . \
+# --build-arg NODE_VERSION=18.19.1 \
+# > 注意阿里云build流水线中,容易丢失环境变量配置,所以直接写在Dockerfile中
+
+# 运行容器
+sudo docker run -d -p 80:80 -p 81:81 -p 443:443 -p 61337:61337 --name edu-textbook-1.0.0 --restart=always fmode:edu-textbook-1.0.0
+sudo docker run -e DATABASE_DBNAME="postgres://postgres:666@192.168.0.177:5432/postgres" -p 80:80 -p 81:81 -p 443:443 -p 61337:61337 --name edu-textbook-1.0.0 --restart=always fmode:edu-textbook-1.0.0
+
+# 导出镜像
+sudo docker save fmode:edu-textbook-1.0.0 -o ../dist/fmode-edu-textbook-1.0.0.tar
+
+```
+
+# 工作流(阿里云镜像构建部署)
+- 后端WebHook
+    - http://flow-openapi.aliyun.com/scm/webhook/536ptuKD686Wn6vEqVIz
+        - https://gitee.com/hep10/tbook-backend.git
+        - 临时地址 http://145.tbook.com.cn/api

+ 5 - 0
docker/sources.list

@@ -0,0 +1,5 @@
+#deb https://mirrors.aliyun.com/debian bullseye main contrib non-free
+#deb https://mirrors.aliyun.com/debian bullseye-updates main contrib non-free
+
+deb http://mirrors.163.com/debian/ bullseye main non-free contrib
+deb http://mirrors.163.com/debian/ bullseye-updates main non-free contrib

+ 5 - 0
docker/update.sh

@@ -0,0 +1,5 @@
+# 更新前后端软件包至opt/
+cd ../
+./deploy-www.sh
+cd server
+./deploy.sh

+ 2 - 1
projects/textbook/src/app/app.component.ts

@@ -17,7 +17,8 @@ export class AppComponent {
   initParseService(){
     Parse.initialize("edu-textbook");
     // (Parse as any).serverURL = ("http://127.0.0.1:61337/parse");
-    (Parse as any).serverURL = ("http://8.140.98.43/parse");
+    // (Parse as any).serverURL = ("http://8.140.98.43/parse");
+    (Parse as any).serverURL = ("https://145.tbook.com.cn/parse");
     localStorage.setItem('company','RbIKpmuaMC')
   }
 

+ 2 - 0
projects/textbook/src/app/comp-upload/comp-upload.component.html

@@ -4,8 +4,10 @@
   (nzChange)="handleChange($event)"
   [nzFileList]="fileList"
   [nzAccept]="'file'"
+  [nzPreview]="preview"
 >
   <div style="color: #3e49b3;cursor: pointer;">
     <span nz-icon nzType="upload"></span>{{title}}
   </div>
+
 </nz-upload>

+ 17 - 3
projects/textbook/src/app/comp-upload/comp-upload.component.ts

@@ -4,11 +4,14 @@ import { CommonCompModule } from '../../services/common.modules';
 import { NzMessageService } from 'ng-zorro-antd/message';
 import { NzUploadChangeParam, NzUploadFile } from 'ng-zorro-antd/upload';
 import { ProvierOssAli } from './provider-oss-aliyun';
+import { NzImageService } from 'ng-zorro-antd/image';
+import { NzImageModule } from 'ng-zorro-antd/image';
+
 
 @Component({
   selector: 'app-comp-upload',
   standalone: true,
-  imports: [NzUploadModule, CommonCompModule],
+  imports: [NzUploadModule, CommonCompModule,NzImageModule],
   templateUrl: './comp-upload.component.html',
   styleUrls: ['./comp-upload.component.scss'],
 })
@@ -27,7 +30,7 @@ export class CompUploadComponent implements OnInit {
     // },
   ];
   ossProvider:{upload:any}|undefined
-  constructor(private msg: NzMessageService) {
+  constructor(private msg: NzMessageService,private nzImageService: NzImageService) {
     this.ossProvider = new ProvierOssAli()
   }
 
@@ -38,12 +41,16 @@ export class CompUploadComponent implements OnInit {
       }
     })
   }
+  ossFileList:any
+  Previewfilelist:any
   async handleChange(info: NzUploadChangeParam) {
     console.log(info);
     if (info.file.status !== 'uploading') {
       // 选择文件后,自动开始上传
       console.log(info.file, info.fileList);
+      this.Previewfilelist = info.fileList
       let ossFileList = await this.uploadAllFileList(info?.fileList)
+      this.ossFileList = ossFileList
       this.change.emit(ossFileList?.map(item=>item?.url))
     }
     if (info.file.status === 'done') {
@@ -53,6 +60,7 @@ export class CompUploadComponent implements OnInit {
     }
 
     // this.change.emit(info.fileList)
+    
   }
 
   async uploadAllFileList(fileList:Array<NzUploadFile>){
@@ -69,7 +77,13 @@ export class CompUploadComponent implements OnInit {
         }
       }
     }
-    console.log(ossFileList)
     return ossFileList
   }
+  /**预览图片 */
+  preview=async (e:any): Promise<void> => {
+    let index = this.Previewfilelist.findIndex((item:any)=>item.uid==e.uid)
+    this.nzImageService.preview([{src:this.ossFileList[index].url}], { nzZoom: 1, nzRotate: 0 });
+  };
+
+
 }

+ 2 - 1
server/cloud/authing/index.js

@@ -1,2 +1,3 @@
 export * from "./func-authing-session-sync"
-export * from "./func-authing-org-sync"
+export * from "./func-authing-org-sync"
+export * from "./trigger-user-save"

+ 78 - 0
server/cloud/authing/trigger-user-save.js

@@ -0,0 +1,78 @@
+const { ManagementClient } = require('authing-node-sdk')
+const managementClient  = new ManagementClient({
+    accessKeyId: '6686bffb373d06911e24a969',
+    accessKeySecret: "4e978331675938d1bc81fb109e67d59a",
+    host: 'https://textbook.u2-dev.hep.com.cn', // 应用的认证地址
+})
+
+export function defineUserAfterSave(){
+    Parse.Cloud.afterSave("_User", async (request) => {
+        // console.log("save _User",request?.object?.id)
+        let query = new Parse.Query("Profile");
+        query.equalTo("user",request?.object?.id)
+        let profile = await query.first();
+       syncUserProfileToAuthing(request?.object,profile)
+    });
+
+    Parse.Cloud.afterSave("Profile", async (request) => {
+        // console.log("save Profile",request?.object?.id)
+        let query = new Parse.Query("Profile");
+        query.include("user");
+        profile = await query.get(request?.object?.id);
+        syncUserProfileToAuthing(profile.get("user"),profile)
+     
+    });
+}
+
+
+async function syncUserProfileToAuthing(user,profile){
+    if(!user?.id) return
+    let userInfo = user.toJSON();
+    userInfo = fixJsonFileds(userInfo)
+    if(profile?.id){
+        let pjson = profile.toJSON();
+        delete pjson.objectId;
+        pjson= fixJsonFileds(pjson)
+        Object.keys(pjson).forEach(key=>{
+            userInfo[key] = pjson[key]
+        })
+    }
+    // 映射对应字段
+    userInfo.company = userInfo.companyName
+    delete userInfo.companyName
+    userInfo.userType = userInfo.identity
+    delete userInfo.identity
+    userInfo.userId = userInfo.objectId
+    delete userInfo.objectId
+    userInfo.identifyStatus = userInfo.accountState
+    // 自定义数据全量同步
+    userInfo.customData = JSON.parse(JSON.stringify(userInfo))
+    // {
+    //     identifyStatus : userInfo.accountState
+    // }
+
+    // console.log(userInfo)
+    // 同步数据至Authing用户池
+    let result
+    try{
+        result = await managementClient.updateUser(userInfo)
+        // console.log(result)
+    }catch(err){console.log(err)}
+
+}
+
+function fixJsonFileds(json){
+    // Parse独有关系数据
+    delete json.ACL
+    delete json.className
+    delete json.sessionToken
+    delete json.company
+    delete json.user
+    delete json.createdAt
+    delete json.updatedAt
+    // 来自Authing的数据
+    delete json?.loginsCount
+    delete json?.lastIP
+    delete json?.lastLogin
+    return json
+}

+ 27 - 0
server/config/certs/tbook.com.cn.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAtePq6l3FFiAZks+LK5wSBJzncGSDR1XcZfzfzZGSDLez8wpW
+O1P5oYnecnuPdBvpefeHmpWw38PXa8lZePpmrLQWFf4dff/37Xbeh0XXvzpGo7x/
+mf4afhFA1y7HQR0sg2et5U8OSglgglmMPM+gP7q7tmd/jHSMp3anDxIuIAnt2+jh
+oDO3yQ6n0Rnktlcs6rFaroBW3klO4MY/UbBS7+yVnJPhHNwZ5qsrIW6ORVZpkXe7
+4zwtj+2vufcT7BQXdew2Y68YTNM0D0GWbe68W5s9zjo5gs6vzj5QunsOGky2waDZ
+SZvH6f7JXoiHAyD3PQQ2BDMhpXNo7H0YurvaIQIDAQABAoIBADM+SF8J1u4iUgId
+7Y/2Ms9Q4xUzhBazVln3UZgJJHPnx084AM5LHL+JZ32qWOJ1wKPHfFEtTfzkoEqy
+Gb5vEUEjpTtB8c4fMJqajBvhLeSmhuM3Rl28jHsq4jEdcS5jiThmG8RILvblyGZA
+0zUIq5V4xsyfQPxxk3TUhUFGNm940g+P02bFRg40XkvzQAK53QZMwyWQgnViQLee
+xBN+sjIhJIHnknKQ3hp4PvaYUJ+gV2Vs2F6qBpLEfet2Odmm77YR8R8eeRB2ES58
+18397b4zPcfIhj6nxrovAeJK5qNTL22nLZxOvNqkmp3KnNZpltLdd2k9/FRSAsoL
+7HjqpN8CgYEA9Ui3HLhztkXoLVeA8ZDmmoXWqLlw4zNiVWyKMxdMD/HbP5c9pu6E
+e+Efi7oRcQKEHcNy9a8CbKDpUkxjUKtdJclnBEs0p3wJ40gcq+/a070QRy23NEp4
+uAxxJfwdQmdHm1euDovvNYrBolr71x/mBbmogB1cgaz3vWZRQzm7AQ8CgYEAvdYz
+FHZg+7P9014E2WmsDpbTz9NGkM6IaZOh7S4opUkErNnY+J4XYmkXt68Vot3pPFrw
+7dZogPguWJ4RlcNFse24IHcWDBX6k5t9Qs1c1hm+AiRGgyXvuHaGJ8sJBJeMa8IK
+GH9DFUgq82NlfswsZwj+b4FW9ARnBfH2lZ9MEc8CgYEAsZn2JFCWUCtsArCyR7cw
+kkBIfqDPUWNaVxohMv8ybvUjWHcNB/r6aKxH6CdRC6/Ts61aZvRSYA9fAd0XqEh8
+SKj884n01TSmADCtuf0RSHFEARXj6nPl1vYDU4qqMPORULR67kN6cB1JOWKBqXGK
+XyjgDeu0mJyCQVx9iJybgBcCgYEAoOnwYmf1zWXEwmXvtlrcfYFcCc4NArIO7280
+0tdNnM1IKTx/rj5TlBeBAzJmJKf2DDMKBpC4BJparsNOJKRhb3ERonNPPO9kd7pV
+c0qgZttrNGWLX5/Ik5PkI9b2ze2oGa9g32MAegmyXpzia5imCrCU+++mxTD2IxP2
+8N0WngECgYEA2psTmArtRSbSizgGHr3AKCpK7MyNmX90tQt2e0jqJtkLTsIC4Ljn
+jtKHIE+uHgtUlqE6GfrM1Kb9yp0p2I3xP2UY5rnQCu0EUJhs7JbeBpbcXGwp9e3l
+rRGxHWGk/xNieHNLBOSFbbqaYX7KUnnX3p9KRjH2RgW9UE1ZW/TNdxU=
+-----END RSA PRIVATE KEY-----

+ 76 - 0
server/config/certs/tbook.com.cn.pem

@@ -0,0 +1,76 @@
+-----BEGIN CERTIFICATE-----
+MIIHmTCCBYGgAwIBAgIQAiNkDIOkp2pc3by/kZ9fujANBgkqhkiG9w0BAQsFADBc
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xNDAyBgNVBAMT
+K1JhcGlkU1NMIEdsb2JhbCBUTFMgUlNBNDA5NiBTSEEyNTYgMjAyMiBDQTEwHhcN
+MjQwNjEyMDAwMDAwWhcNMjUwNjEyMjM1OTU5WjAZMRcwFQYDVQQDDA4qLnRib29r
+LmNvbS5jbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALXj6updxRYg
+GZLPiyucEgSc53Bkg0dV3GX8382Rkgy3s/MKVjtT+aGJ3nJ7j3Qb6Xn3h5qVsN/D
+12vJWXj6Zqy0FhX+HX3/9+123odF1786RqO8f5n+Gn4RQNcux0EdLINnreVPDkoJ
+YIJZjDzPoD+6u7Znf4x0jKd2pw8SLiAJ7dvo4aAzt8kOp9EZ5LZXLOqxWq6AVt5J
+TuDGP1GwUu/slZyT4RzcGearKyFujkVWaZF3u+M8LY/tr7n3E+wUF3XsNmOvGEzT
+NA9Blm3uvFubPc46OYLOr84+ULp7DhpMtsGg2Umbx+n+yV6IhwMg9z0ENgQzIaVz
+aOx9GLq72iECAwEAAaOCA5gwggOUMB8GA1UdIwQYMBaAFPCchf2in32PyWi71dSJ
+TR2+05D/MB0GA1UdDgQWBBQLl+iCJuHMELaa6FvsC+aJQx7yxjAnBgNVHREEIDAe
+gg4qLnRib29rLmNvbS5jboIMdGJvb2suY29tLmNuMD4GA1UdIAQ3MDUwMwYGZ4EM
+AQIBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAO
+BgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIGf
+BgNVHR8EgZcwgZQwSKBGoESGQmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9SYXBp
+ZFNTTEdsb2JhbFRMU1JTQTQwOTZTSEEyNTYyMDIyQ0ExLmNybDBIoEagRIZCaHR0
+cDovL2NybDQuZGlnaWNlcnQuY29tL1JhcGlkU1NMR2xvYmFsVExTUlNBNDA5NlNI
+QTI1NjIwMjJDQTEuY3JsMIGHBggrBgEFBQcBAQR7MHkwJAYIKwYBBQUHMAGGGGh0
+dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBRBggrBgEFBQcwAoZFaHR0cDovL2NhY2Vy
+dHMuZGlnaWNlcnQuY29tL1JhcGlkU1NMR2xvYmFsVExTUlNBNDA5NlNIQTI1NjIw
+MjJDQTEuY3J0MAwGA1UdEwEB/wQCMAAwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoB
+aAB2AE51oydcmhDDOFts1N8/Uusd8OCOG41pwLH6ZLFimjnfAAABkAo7NywAAAQD
+AEcwRQIhAKzbUMfOP4tk//qVKr94J376s0lfHPAiguD1abr/chCHAiAIP4e8bP/W
+RyS/SANEInQFYcrmAb0yRh3lgXX+4ftXdgB2AObSMWNAd4zBEEEG13G5zsHSQPaW
+hIb7uocyHf0eN45QAAABkAo7NrkAAAQDAEcwRQIhAJIYUzYe2GqIIoQWn+Wq4Twy
+DR87xr0depKt0peOhtA0AiBm1eJPfrud4dZq/dJ4LadjGGAndMYPwVbIwg9EshGO
+pAB2AKLjCuRF772tm3447Udnd1PXgluElNcrXhssxLlQpEfnAAABkAo7OCkAAAQD
+AEcwRQIhAN5OEYfNJ2RUeFIYOIDut3uBuVjSyumLsbm5jnbidWtuAiBAGCrHPeaD
+4m2v7vm6Qkfsq3nzwCjccr/6Ep/b4NsZJTANBgkqhkiG9w0BAQsFAAOCAgEAdE1A
++wQjDwk2qdLFTZgj/Tu807/kXhunohLl/jiSvb0YREpZB03puCxagHrw3kG2QwKP
+22RiPAA4IbU+y3y3fI/CGO0jCpW8I+Z6+suaefrxiHrPtZqlvgOrn7YHmsKSR1nY
+MnT0S7IPOnRDDXs+rMTiUOs2h5IThjD3zxTIBFerLJKr4CU1gieBjY+1Yt3g4cFg
+90gB5HuOlq5mtmYLJ9LeymM6kS2TqzpNSf+VILlNXyA3IwzpbtY4cZQDg8zpXiPo
+d1GnM0QpYlJ71+Pf5qLoIk+aLr2MAmfDNks4M9LI17VjtsfrhzPzeCU2Z5NJSO8p
+g5QZDZoCPouh1jb0ihQSZFuHhyDl7i49RyVNDdMOCa1Aq9nio9qPKNYTrdxRjmyg
+sOrqu0c9wQI7JU0LMJXjuthjNuSC3cIRbHR3MWMr7xNPSIpF0Al1XXWFXFjVEyGP
+x8GuZ9PJLcdsC9dt+xwW8Aj9vfoGV5I031x8OActlgIGEid1TWI5hU7BIQelqVau
+k/DNUTPSzs1nR0OHYJ/cGBhPjq3EtZZ91R9W/QOfDiOXyUwLugIfRMchGgUOJflx
+THgAw/PW5n7PfrhKloo5aMgP/L1MeSxzR63qsxvtizPg1mA/eR5S3z8PUiOzoBPt
+8+dwDm/ELsep9mL9DidmjVnXVtfgggC8uUSLavk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFyzCCBLOgAwIBAgIQCgWbJfVLPYeUzGYxR3U4ozANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0yMjA1MDQwMDAwMDBaFw0zMTExMDkyMzU5NTlaMFwxCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE0MDIGA1UEAxMrUmFwaWRTU0wgR2xv
+YmFsIFRMUyBSU0E0MDk2IFNIQTI1NiAyMDIyIENBMTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBAKY5PJhwCX2UyBb1nelu9APen53D5+C40T+BOZfSFaB0
+v0WJM3BGMsuiHZX2IHtwnjUhLL25d8tgLASaUNHCBNKKUlUGRXGztuDIeXb48d64
+k7Gk7u7mMRSrj+yuLSWOKnK6OGKe9+s6oaVIjHXY+QX8p2I2S3uew0bW3BFpkeAr
+LBCU25iqeaoLEOGIa09DVojd3qc/RKqr4P11173R+7Ub05YYhuIcSv8e0d7qN1sO
+1+lfoNMVfV9WcqPABmOasNJ+ol0hAC2PTgRLy/VZo1L0HRMr6j8cbR7q0nKwdbn4
+Ar+ZMgCgCcG9zCMFsuXYl/rqobiyV+8U37dDScAebZTIF/xPEvHcmGi3xxH6g+dT
+CjetOjJx8sdXUHKXGXC9ka33q7EzQIYlZISF7EkbT5dZHsO2DOMVLBdP1N1oUp0/
+1f6fc8uTDduELoKBRzTTZ6OOBVHeZyFZMMdi6tA5s/jxmb74lqH1+jQ6nTU2/Mma
+hGNxUuJpyhUHezgBA6sto5lNeyqc+3Cr5ehFQzUuwNsJaWbDdQk1v7lqRaqOlYjn
+iomOl36J5txTs0wL7etCeMRfyPsmc+8HmH77IYVMUOcPJb+0gNuSmAkvf5QXbgPI
+Zursn/UYnP9obhNbHc/9LYdQkB7CXyX9mPexnDNO7pggNA2jpbEarLmZGi4grMmf
+AgMBAAGjggGCMIIBfjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTwnIX9
+op99j8lou9XUiU0dvtOQ/zAfBgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3R
+VTAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC
+MHYGCCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl
+cnQuY29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v
+RGlnaUNlcnRHbG9iYWxSb290Q0EuY3J0MEIGA1UdHwQ7MDkwN6A1oDOGMWh0dHA6
+Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwPQYD
+VR0gBDYwNDALBglghkgBhv1sAgEwBwYFZ4EMAQEwCAYGZ4EMAQIBMAgGBmeBDAEC
+AjAIBgZngQwBAgMwDQYJKoZIhvcNAQELBQADggEBAAfjh/s1f5dDdfm0sNm74/dW
+MbbsxfYV1LoTpFt+3MSUWvSbiPQfUkoV57b5rutRJvnPP9mSlpFwcZ3e1nSUbi2o
+ITGA7RCOj23I1F4zk0YJm42qAwJIqOVenR3XtyQ2VR82qhC6xslxtNf7f2Ndx2G7
+Mem4wpFhyPDT2P6UJ2MnrD+FC//ZKH5/ERo96ghz8VqNlmL5RXo8Ks9rMr/Ad9xw
+Y4hyRvAz5920myUffwdUqc0SvPlFnahsZg15uT5HkK48tHR0TLuLH8aRpzh4KJ/Y
+p0sARNb+9i1R4Fg5zPNvHs2BbIve0vkwxAy+R4727qYzl3027w9jEFC6HMXRaDc=
+-----END CERTIFICATE-----

+ 9 - 152
server/config/nginx-server.conf

@@ -1,122 +1,3 @@
-# server {
-
-#     server_name _;
-#     server_tokens off;
-
-#     #large_client_header_buffers 4 32k;
-#     client_max_body_size 50M;
-
-#     charset utf-8;
-
-#     index index.html;
-
-
-#     # 启动SSL及证书匹配 ######################################################
-#     listen 443 ssl default_server; # managed by Certbot
-    
-#     # ssl_certificate /etc/letsencrypt/live/edutextbook/fullchain.pem; # managed by Certbot
-#     # ssl_certificate_key /etc/letsencrypt/live/edutextbook/privkey.pem; # managed by Certbot
-
-#     # include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
-#     # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
-
-# 	# 启用OCSP stapling ######################################################
-# 	ssl_stapling on;
-# 	ssl_stapling_verify on;
-# 	# valid表示缓存5分钟
-# 	resolver 8.8.8.8 8.8.4.4 valid=300s;
-# 	# 网络超时时间
-# 	resolver_timeout 5s;
-
-# 	# 启动Gzip Json模式 ######################################################
-# 	gzip_http_version 1.0;  # gzip支持http协议 proxy 必须用
-#     gzip  on;
-# 	gzip_vary on;
-# 	gzip_proxied       any;
-# 	gzip_static on;
-# 	gzip_comp_level  4;
-# 	gzip_min_length 256;
-#     gzip_buffers     4 8k;
-#     gzip_types       text/html text/plain application/javascript application/x-javascript text/css application/xml application/json;
-
-#     location /api {
-#         if ($request_method = 'OPTIONS') {
-#             add_header 'Access-Control-Allow-Origin' '*';
-#             add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-#             #
-#             # Custom headers and headers various browsers *should* be OK with but aren't
-#             #
-#             add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
-#             #
-#             # Tell client that this pre-flight info is valid for 20 days
-#             #
-#             add_header 'Access-Control-Max-Age' 1728000;
-#             add_header 'Content-Type' 'text/plain; charset=utf-8';
-#             add_header 'Content-Length' 0;
-#             return 204;
-#         }
-#         if ($request_method = 'POST') {
-#             add_header 'Access-Control-Allow-Origin' '*' always;
-#             add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
-#             add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Access-Control-Allow-Origin' always;
-#             add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
-#         }
-#         if ($request_method = 'GET') {
-#             add_header 'Access-Control-Allow-Origin' '*' always;
-#             add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
-#             add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Access-Control-Allow-Origin' always;
-#             add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
-#         }
-
-#         rewrite ^/(.*)$ /$1 break;
-#         proxy_pass http://127.0.0.1:61337/api;
-#     }
-
-#     location /parse {
-#         if ($request_method = 'OPTIONS') {
-#             add_header 'Access-Control-Allow-Origin' '*';
-#             add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-#             #
-#             # Custom headers and headers various browsers *should* be OK with but aren't
-#             #
-#             add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
-#             #
-#             # Tell client that this pre-flight info is valid for 20 days
-#             #
-#             add_header 'Access-Control-Max-Age' 1728000;
-#             add_header 'Content-Type' 'text/plain; charset=utf-8';
-#             add_header 'Content-Length' 0;
-#             return 204;
-#         }
-#         if ($request_method = 'POST') {
-#             add_header 'Access-Control-Allow-Origin' '*' always;
-#             add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
-#             add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Access-Control-Allow-Origin' always;
-#             add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
-#         }
-#         if ($request_method = 'GET') {
-#             add_header 'Access-Control-Allow-Origin' '*' always;
-#             add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
-#             add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Access-Control-Allow-Origin' always;
-#             add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
-#         }
-
-#         rewrite ^/(.*)$ /$1 break;
-#         proxy_pass http://127.0.0.1:61337/parse;
-#     }
-
-#     location /{
-#         proxy_set_header Host $http_host;
-#         proxy_set_header X-Real-IP $remote_addr;
-#         proxy_set_header X-Scheme $scheme;
-#         proxy_set_header X-Forwarded-Proto $scheme;
-#         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-#         proxy_pass http://127.0.0.1:61337/;
-#         proxy_redirect off;
-#     }
-
-# }
-
 
 server {
     listen 80 default_server;
@@ -132,39 +13,6 @@ server {
 	gzip_min_length 256;
     gzip_buffers     4 8k;
     gzip_types       text/html text/plain application/javascript application/x-javascript text/css application/xml application/json;
-    
-    location /api {
-        if ($request_method = 'OPTIONS') {
-            add_header 'Access-Control-Allow-Origin' '*';
-            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-            #
-            # Custom headers and headers various browsers *should* be OK with but aren't
-            #
-            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
-            #
-            # Tell client that this pre-flight info is valid for 20 days
-            #
-            add_header 'Access-Control-Max-Age' 1728000;
-            add_header 'Content-Type' 'text/plain; charset=utf-8';
-            add_header 'Content-Length' 0;
-            return 204;
-        }
-        if ($request_method = 'POST') {
-            add_header 'Access-Control-Allow-Origin' '*' always;
-            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
-            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Access-Control-Allow-Origin' always;
-            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
-        }
-        if ($request_method = 'GET') {
-            add_header 'Access-Control-Allow-Origin' '*' always;
-            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
-            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Access-Control-Allow-Origin' always;
-            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
-        }
-
-        rewrite ^/(.*)$ /$1 break;
-        proxy_pass http://127.0.0.1:61337/api;
-    }
 
     location /parse/{
         proxy_set_header Host $http_host;
@@ -175,6 +23,15 @@ server {
         proxy_pass http://127.0.0.1:61337/parse/;
         proxy_redirect off;
     }
+    location /api/parse/{
+        proxy_set_header Host $http_host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Scheme $scheme;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_pass http://127.0.0.1:61337/parse/;
+        proxy_redirect off;
+    }
 
     root /var/www/edu-textbook/;
     location /{

+ 25 - 5
server/deploy.sh

@@ -7,12 +7,32 @@ PROD_HOST=root@8.140.98.43
 # 部署路径
 DIR_SERVER=/opt/edu-textbook-server
 
-# 部署过程
+# 资源文件
+cp -rf config dist/server/
+cp package.json dist/server/
+sed -i '/@embedded/d' dist/server/package.json
+sed -i '/@embedded/d' dist/server/package.json
+cp config.js dist/server/
+cp keepalive.js dist/server/
+cd dist
+tar zcvf server.tar.gz ./server
+cp server.tar.gz ../../docker/opt/
+cd ..
+
+# 压缩部署
 ssh $DEV_HOST "mkdir -p $DIR_SERVER"
-scp dist/server/server.js $DEV_HOST:$DIR_SERVER/
-scp ./package.json $DEV_HOST:$DIR_SERVER/
-scp ./config.js $DEV_HOST:$DIR_SERVER/
-ssh $DEV_HOST "scp -r $DIR_SERVER $PROD_HOST:/opt/"
+scp dist/server.tar.gz $DEV_HOST:$DIR_SERVER/
+ssh $DEV_HOST "scp -r $DIR_SERVER/server.tar.gz $PROD_HOST:/opt/"
+ssh $DEV_HOST "ssh $PROD_HOST \"cd /opt/ && tar zxvf server.tar.gz && cp -rf server/* edu-textbook-server/\"" # 仅安装prod
+ssh $DEV_HOST "ssh $PROD_HOST \"cd /opt/ && cp server.tar.gz /var/www/edu-textbook\"" # 仅安装prod
+
+
+# 部署过程
+# ssh $DEV_HOST "mkdir -p $DIR_SERVER"
+# scp dist/server/server.js $DEV_HOST:$DIR_SERVER/
+# scp ./package.json $DEV_HOST:$DIR_SERVER/
+# scp ./config.js $DEV_HOST:$DIR_SERVER/
+# ssh $DEV_HOST "scp -r $DIR_SERVER $PROD_HOST:/opt/"
 
 # 启动过程
 ssh $DEV_HOST "ssh $PROD_HOST \"cd $DIR_SERVER && ls -lah\"" # 仅安装prod

+ 71 - 0
server/keepalive.js

@@ -0,0 +1,71 @@
+const schedule = require('node-schedule');
+const request = require("request");
+const shell = require("shelljs")
+const exec = require('child_process').exec;
+
+// const express = require('express');
+// const app = express();
+
+    let appList = [
+        {host:"127.0.0.1",port:"61337",httpchk:"/parse",name:"server",cmd:"pm2 restart server"},
+    ]
+const keepaliveTask = ()=>{
+    shell.exec(`cd /opt/edu-textbook-server && pm2 start server.js -i max`,{silent:true})
+    //每分钟的1-10秒都会触发,其它通配符依次类推
+    schedule.scheduleJob('*/10 * * * * *', ()=>{
+        appList.forEach(app=>{
+            checkHttpAlive(app);
+        })
+    })
+}
+keepaliveTask()
+
+
+async function checkHttpAlive(app){
+    let info = shell.exec(`pm2 info ${app.name}`,{ silent: true })
+    // pm2服务不存在,无需检查
+    if(!info?.stdout){
+        return
+    }
+
+    // pm2服务存在,判断是否正常
+    jsonRequest(`http://${app.host}:${app.port}${app.httpchk}`,"GET").then(data=>{
+        // console.log(data)
+    }).catch(err=>{
+        console.log(err)
+        console.log("开始重启->"+app.host);
+        let com = exec(`${app.cmd}`)
+        com.stdout.on('data', function (data) {
+            console.log("重启成功->"+data);
+            // res();
+        });
+
+        com.stderr.on('data', function (data) {
+            console.log('重启出错->' + data);
+            // res();
+        });
+    })
+}
+
+function jsonRequest(url,method, body){
+    body = body || {}
+    method = method || "GET"
+    return new Promise((resolve,reject)=>{
+        request({
+            url: url,
+            method: method,
+            json:true,
+            headers:{
+                "Content-Type": "application/json"
+            },
+            body:body
+        },(error, response, body) => {
+            if (!error && response.statusCode == 200) {
+                resolve(body)
+            }else{
+                console.error(error)
+                reject(error)
+            }
+        })
+    })
+}

+ 87 - 74
server/package-lock.json

@@ -9,20 +9,21 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
-        "@embedded-postgres/linux-x64": "^16.2.0-beta.11",
-        "@embedded-postgres/windows-x64": "^16.2.0-beta.11",
         "ali-oss": "^6.20.0",
-        "aliyun-sdk": "^1.12.10",
         "authing-js-sdk": "^4.23.51",
         "authing-node-sdk": "^3.1.0",
+        "node-schedule": "^2.1.1",
         "parse-dashboard": "^5.4.0",
         "parse-server": "^7.0.0",
+        "shelljs": "^0.8.5",
         "yargs": "17.7.2"
       },
       "bin": {
         "server": "server.js"
       },
       "devDependencies": {
+        "@embedded-postgres/linux-x64": "^16.2.0-beta.11",
+        "@embedded-postgres/windows-x64": "^16.2.0-beta.11",
         "pkg": "^5.8.1",
         "vite": "^4.5.3",
         "vite-plugin-dts": "^3.9.1",
@@ -742,6 +743,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "hasInstallScript": true,
       "os": [
         "linux"
@@ -757,6 +759,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "hasInstallScript": true,
       "os": [
         "win32"
@@ -3450,43 +3453,6 @@
         "node": ">=4.0.0"
       }
     },
-    "node_modules/aliyun-sdk": {
-      "version": "1.12.10",
-      "resolved": "https://registry.npmmirror.com/aliyun-sdk/-/aliyun-sdk-1.12.10.tgz",
-      "integrity": "sha512-uTuDRw+YiZSiKoHl9wg8SaUvCyh2lj470X/piz8ZExscJal1D5JE95O4PoMAebLM9tGOd+X47vbQWe1q3wPxpg==",
-      "dependencies": {
-        "node_memcached": "1.1.3",
-        "pomelo-protobuf": "^0.4.0",
-        "protobufjs": ">=5.0.3",
-        "xml2js": "0.4.4",
-        "xmlbuilder": "^13.0.2"
-      },
-      "engines": {
-        "node": ">= 0.6.0"
-      }
-    },
-    "node_modules/aliyun-sdk/node_modules/sax": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmmirror.com/sax/-/sax-0.6.1.tgz",
-      "integrity": "sha512-8ip+qnRh7m8OEyvoM1JoSBzlrepp3ajVR8nqgrfTig+TewfyvTijl0am8/anFqgbcdz62ofEUKE1hHNDCdbeSQ=="
-    },
-    "node_modules/aliyun-sdk/node_modules/xml2js": {
-      "version": "0.4.4",
-      "resolved": "https://registry.npmmirror.com/xml2js/-/xml2js-0.4.4.tgz",
-      "integrity": "sha512-9ERdxLOo4EazMDHAS/vsuZiTXIMur6ydcRfzGrFVJ4qM78zD3ohUgPJC7NYpGwd5rnS0ufSydMJClh6jyH+V0w==",
-      "dependencies": {
-        "sax": "0.6.x",
-        "xmlbuilder": ">=1.0.0"
-      }
-    },
-    "node_modules/aliyun-sdk/node_modules/xmlbuilder": {
-      "version": "13.0.2",
-      "resolved": "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-13.0.2.tgz",
-      "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==",
-      "engines": {
-        "node": ">=6.0"
-      }
-    },
     "node_modules/ansi-escapes": {
       "version": "4.3.2",
       "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -4491,6 +4457,17 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/cron-parser": {
+      "version": "4.9.0",
+      "resolved": "https://registry.npmmirror.com/cron-parser/-/cron-parser-4.9.0.tgz",
+      "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
+      "dependencies": {
+        "luxon": "^3.2.1"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
     "node_modules/cross-fetch": {
       "version": "3.1.8",
       "resolved": "https://registry.npmmirror.com/cross-fetch/-/cross-fetch-3.1.8.tgz",
@@ -6279,7 +6256,6 @@
       "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
       "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
       "deprecated": "Glob versions prior to v9 are no longer supported",
-      "peer": true,
       "dependencies": {
         "fs.realpath": "^1.0.0",
         "inflight": "^1.0.4",
@@ -6974,6 +6950,14 @@
         "node": ">=8"
       }
     },
+    "node_modules/interpret": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/interpret/-/interpret-1.4.0.tgz",
+      "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
     "node_modules/intersect": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/intersect/-/intersect-1.0.1.tgz",
@@ -8013,6 +7997,11 @@
       "resolved": "https://registry.npmmirror.com/long/-/long-4.0.0.tgz",
       "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
     },
+    "node_modules/long-timeout": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmmirror.com/long-timeout/-/long-timeout-0.1.1.tgz",
+      "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w=="
+    },
     "node_modules/loose-envify": {
       "version": "1.4.0",
       "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -8068,6 +8057,14 @@
         "es5-ext": "~0.10.2"
       }
     },
+    "node_modules/luxon": {
+      "version": "3.4.4",
+      "resolved": "https://registry.npmmirror.com/luxon/-/luxon-3.4.4.tgz",
+      "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/magic-string": {
       "version": "0.30.10",
       "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.10.tgz",
@@ -8462,27 +8459,6 @@
       "resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz",
       "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
     },
-    "node_modules/node_memcached": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmmirror.com/node_memcached/-/node_memcached-1.1.3.tgz",
-      "integrity": "sha512-eh4G16ZgKEUzOQLHi3Mn6NVJhT59HkzAOzM8SpENNylPxFdbbmRR4ZhQmm4s70ZV9bY/ul89o1KFbmGyS9qfXw==",
-      "dependencies": {
-        "debug": "^2.1.0"
-      }
-    },
-    "node_modules/node_memcached/node_modules/debug": {
-      "version": "2.6.9",
-      "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
-      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-      "dependencies": {
-        "ms": "2.0.0"
-      }
-    },
-    "node_modules/node_memcached/node_modules/ms": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-    },
     "node_modules/node-abi": {
       "version": "3.65.0",
       "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.65.0.tgz",
@@ -8560,6 +8536,19 @@
       "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
       "peer": true
     },
+    "node_modules/node-schedule": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/node-schedule/-/node-schedule-2.1.1.tgz",
+      "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==",
+      "dependencies": {
+        "cron-parser": "^4.2.0",
+        "long-timeout": "0.1.1",
+        "sorted-array-functions": "^1.3.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/nopt": {
       "version": "6.0.0",
       "resolved": "https://registry.npmmirror.com/nopt/-/nopt-6.0.0.tgz",
@@ -9438,7 +9427,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
       "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
-      "peer": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -9455,8 +9443,7 @@
     "node_modules/path-parse": {
       "version": "1.0.7",
       "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
-      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
-      "dev": true
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
     },
     "node_modules/path-to-regexp": {
       "version": "6.2.1",
@@ -9939,11 +9926,6 @@
         "node": ">=10.13.0"
       }
     },
-    "node_modules/pomelo-protobuf": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmmirror.com/pomelo-protobuf/-/pomelo-protobuf-0.4.0.tgz",
-      "integrity": "sha512-QbAdJad/QpoGc4xZsHrrYWrpNtr4d1Qzb8b/5uU5BYF3TX4eBmBX5PiFmFq9JDucC38k8Q7PsTe+EN8QVnjldQ=="
-    },
     "node_modules/possible-typed-array-names": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
@@ -10140,6 +10122,7 @@
       "resolved": "https://registry.npmmirror.com/protobufjs/-/protobufjs-7.3.2.tgz",
       "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==",
       "hasInstallScript": true,
+      "optional": true,
       "dependencies": {
         "@protobufjs/aspromise": "^1.1.2",
         "@protobufjs/base64": "^1.1.2",
@@ -10161,7 +10144,8 @@
     "node_modules/protobufjs/node_modules/long": {
       "version": "5.2.3",
       "resolved": "https://registry.npmmirror.com/long/-/long-5.2.3.tgz",
-      "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
+      "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
+      "optional": true
     },
     "node_modules/proxy-addr": {
       "version": "2.0.7",
@@ -10794,6 +10778,17 @@
         "node": ">= 6"
       }
     },
+    "node_modules/rechoir": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmmirror.com/rechoir/-/rechoir-0.6.2.tgz",
+      "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
+      "dependencies": {
+        "resolve": "^1.1.6"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
     "node_modules/redis": {
       "version": "4.6.13",
       "resolved": "https://registry.npmmirror.com/redis/-/redis-4.6.13.tgz",
@@ -10929,7 +10924,6 @@
       "version": "1.22.8",
       "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",
       "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
-      "dev": true,
       "dependencies": {
         "is-core-module": "^2.13.0",
         "path-parse": "^1.0.7",
@@ -10960,7 +10954,6 @@
       "version": "2.14.0",
       "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.14.0.tgz",
       "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==",
-      "dev": true,
       "dependencies": {
         "hasown": "^2.0.2"
       },
@@ -11336,6 +11329,22 @@
         "node": ">=8"
       }
     },
+    "node_modules/shelljs": {
+      "version": "0.8.5",
+      "resolved": "https://registry.npmmirror.com/shelljs/-/shelljs-0.8.5.tgz",
+      "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
+      "dependencies": {
+        "glob": "^7.0.0",
+        "interpret": "^1.0.0",
+        "rechoir": "^0.6.2"
+      },
+      "bin": {
+        "shjs": "bin/shjs"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/side-channel": {
       "version": "1.0.6",
       "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz",
@@ -11461,6 +11470,11 @@
         "npm": ">= 3.0.0"
       }
     },
+    "node_modules/sorted-array-functions": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
+      "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
+    },
     "node_modules/source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
@@ -11810,7 +11824,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
-      "dev": true,
       "engines": {
         "node": ">= 0.4"
       },

+ 4 - 2
server/package.json

@@ -28,16 +28,18 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
-    "@embedded-postgres/linux-x64": "^16.2.0-beta.11",
-    "@embedded-postgres/windows-x64": "^16.2.0-beta.11",
     "ali-oss": "^6.20.0",
     "authing-js-sdk": "^4.23.51",
     "authing-node-sdk": "^3.1.0",
+    "node-schedule": "^2.1.1",
     "parse-dashboard": "^5.4.0",
     "parse-server": "^7.0.0",
+    "shelljs": "^0.8.5",
     "yargs": "17.7.2"
   },
   "devDependencies": {
+    "@embedded-postgres/linux-x64": "^16.2.0-beta.11",
+    "@embedded-postgres/windows-x64": "^16.2.0-beta.11",
     "pkg": "^5.8.1",
     "vite": "^4.5.3",
     "vite-plugin-dts": "^3.9.1",

+ 5 - 3
server/server.js

@@ -46,6 +46,7 @@ import  {textbookRouter} from "./api/textbook/routes";
 import { defineAliOssSTS } from "./cloud/aliyun"
 import { defineAuthingLogin } from "./cloud/authing"
 import { defineAuthingDepartSync } from "./cloud/authing"
+import { defineUserAfterSave } from "./cloud/authing"
 import { importData } from "./db/func/import-data"
 
 
@@ -177,9 +178,10 @@ async function initParseAndDatabase(){
         importData()
       }, 500);
       // 导入CloudCode
-      defineAuthingLogin()
-      defineAuthingDepartSync()
-      defineAliOssSTS()
+      defineAuthingLogin();
+      defineAuthingDepartSync();
+      defineAliOssSTS();
+      defineUserAfterSave();
     });
  
     console.log("正在启动管理看板...")

+ 27 - 27
server/vite.config.js

@@ -13,33 +13,33 @@ export default defineConfig({
             exportName:"EduTextbookServer",
             // initAppOnBoot:false,
         }),
-        // obfuscatorPlugin({
-        //     include: ["**/*.js"],
-        //     exclude: [/node_modules/],
-        //     apply: "build",
-        //     debugger: true,
-        //     options: {
-        //         debugProtection:false,
-        //         debugProtectionInterval:0,
-        //         // ...  [See more options](https://github.com/javascript-obfuscator/javascript-obfuscator)
-        //         compact: true,
-        //         controlFlowFlattening: false,
-        //         controlFlowFlatteningThreshold: 0.5,
-        //         deadCodeInjection: false,
-        //         deadCodeInjectionThreshold: 0.2,
-        //         disableConsoleOutput: true,
-        //         identifierNamesGenerator: 'hexadecimal',
-        //         log: true,
-        //         renameGlobals: false,
-        //         rotateStringArray: true,
-        //         selfDefending: false,
-        //         stringArray: true,
-        //         stringArrayEncoding: ['base64'],
-        //         stringArrayThreshold: 0.5,
-        //         transformObjectKeys: false,
-        //         unicodeEscapeSequence: false
-        //     },
-        // }),
+        obfuscatorPlugin({
+            include: ["**/*.js"],
+            exclude: [/node_modules/],
+            apply: "build",
+            debugger: true,
+            options: {
+                debugProtection:false,
+                debugProtectionInterval:0,
+                // ...  [See more options](https://github.com/javascript-obfuscator/javascript-obfuscator)
+                compact: true,
+                controlFlowFlattening: false,
+                controlFlowFlatteningThreshold: 0.5,
+                deadCodeInjection: false,
+                deadCodeInjectionThreshold: 0.2,
+                disableConsoleOutput: false, // 禁用日志输出,调试时关闭
+                identifierNamesGenerator: 'hexadecimal',
+                log: true,
+                renameGlobals: false,
+                rotateStringArray: true,
+                selfDefending: false,
+                stringArray: true,
+                stringArrayEncoding: ['base64'],
+                stringArrayThreshold: 0.5,
+                transformObjectKeys: false,
+                unicodeEscapeSequence: false
+            },
+        }),
     ],
     build: {
         lib: {