Delancey 6 månader sedan
förälder
incheckning
c13317bd5b

+ 57 - 0
docs-prod/schema.md

@@ -132,3 +132,60 @@ Post "0..*" -- "0..*" Tag : tagged with
 - **第二范式**:所有非主键字段都完全依赖于主键,确保没有部分依赖。
 - **第三范式**:没有传递依赖,所有非主键字段都直接依赖于主键。
 
+
+```plantuml
+@startuml
+class User {
+    - userId: int
+    - username: String
+    - password: String
+    + postComment(ScenicSpot spot, String content)
+    + giveFeedback(Feedback feedback)
+    + subscribe(ScenicSpot spot)
+}
+
+class ScenicSpot {
+    - spotId: int
+    - spotName: String
+    - location: String
+    + addPost(Post post)
+}
+
+class Post {
+    - postId: int
+    - content: String
+    - postTime: Date
+    + publishAnnouncement(ScenicSpot spot, String content)
+}
+
+class Feedback {
+    - feedbackId: int
+    - feedbackContent: String
+    - feedbackTime: Date
+}
+
+class Subscription {
+    - subscriptionId: int
+    - user: User
+    - scenicSpot: ScenicSpot
+}
+
+class WeatherService {
+    + getWeather(ScenicSpot spot)
+}
+
+class AGIPlanner {
+    + generateItinerary(User user, ScenicSpot[] spots)
+}
+
+User "1" *-- "0..*" Post
+User "1" *-- "0..*" Feedback
+User "1" *-- "0..*" Subscription
+ScenicSpot "1" *-- "0..*" Post
+Subscription "1" *-- "1" User
+Subscription "1" *-- "1" ScenicSpot
+WeatherService -- ScenicSpot
+AGIPlanner -- User
+AGIPlanner -- ScenicSpot
+@enduml
+```

+ 0 - 0
travel-app/src/lib/ncloud.ts


+ 163 - 0
travel-server/lib/ncloud.js

@@ -0,0 +1,163 @@
+class CloudObject{
+    id
+    className
+    data = {}
+    constructor(className){
+        this.className = className
+    }
+    toPointer(){
+        return {"__type":"Pointer","className":this.className,"objectId":this.id}
+    }
+    set(json){
+        Object.keys(json).forEach(key=>{
+            if(["objectId","id","createdAt","updatedAt","ACL"].indexOf(key)>-1){
+                return
+            }
+            this.data[key] = json[key]
+        })
+    }
+    get(key){
+        return this.data[key] || null
+    }
+    async save(){
+        let method = "POST"
+        let url = "http://dev.fmode.cn:1337/parse/classes/" + this.className
+        // 更新
+        if(this.id){
+            url += "/"+this.id
+            method = "PUT"
+        } 
+        let body = JSON.stringify(this.data)
+        let response = await fetch(url, {
+            "headers": {
+              "content-type": "application/json;charset=UTF-8",
+              "x-parse-application-id": "dev"
+            },
+            "body": body,
+            "method": method,
+            "mode": "cors",
+            "credentials": "omit"
+          });
+          let result = await response?.json();
+          if(result?.error){
+            console.error(result?.error)
+          }
+          if(result?.objectId){this.id = result?.objectId}
+          return this
+    }
+    async destory(){
+        if(!this.id) return
+        let response = await fetch("http://dev.fmode.cn:1337/parse/classes/Doctor/"+this.id, {
+            "headers": {
+              "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "DELETE",
+            "mode": "cors",
+            "credentials": "omit"
+          });
+          let result = await response?.json();
+          if(result){
+            this.id = null
+        }
+        return true
+    }
+}
+
+class CloudQuery{
+    className
+    constructor(className){
+        this.className = className
+    }
+
+    whereOptions = {}
+    greaterThan(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gt"] = value
+    }
+    greaterThanAndEqualTo(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gte"] = value
+    }
+    lessThan(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lt"] = value
+    }
+    lessThanAndEqualTo(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lte"] = value
+    }
+    equalTo(key,value){
+        this.whereOptions[key] = value
+    }
+
+    async get(id){
+        let url = "http://dev.fmode.cn:1337/parse/classes/"+this.className+"/"+id+"?"
+
+        let response = await fetch(url, {
+            "headers": {
+            "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+            "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        return json || {}
+    }
+    async find(){
+        let url = "http://dev.fmode.cn:1337/parse/classes/"+this.className+"?"
+        
+        if(Object.keys(this.whereOptions)?.length){
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+        }
+
+        let response = await fetch(url, {
+            "headers": {
+            "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+            "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        return json?.results || []
+    }
+    async first(){
+        let url = "http://dev.fmode.cn:1337/parse/classes/"+this.className+"?"
+        
+        if(Object.keys(this.whereOptions)?.length){
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+        }
+
+        let response = await fetch(url, {
+            "headers": {
+            "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+            "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        let exists = json?.results?.[0] || null
+        if(exists){
+            let existsObject = new CloudObject(this.className)
+            existsObject.set(exists)
+            existsObject.id = exists.objectId
+            existsObject.createdAt = exists.createdAt
+            existsObject.updatedAt = exists.updatedAt
+            return existsObject
+        }
+    }
+}
+
+module.exports.CloudObject = CloudObject
+module.exports.CloudQuery = CloudQuery

+ 75 - 0
travel-server/migration/data.js

@@ -0,0 +1,75 @@
+module.exports.UserList = [
+    {
+        "objectId": "user1",
+        "createdAt": "2024-01-01T10:00:00.000Z",
+        "username": "traveler_01",
+        "email": "traveler01@example.com",
+        "profilePicture": {"__type": "Pointer", "className": "Image", "objectId": "img1"}
+    },
+    {
+        "objectId": "user2",
+        "createdAt": "2024-01-02T11:00:00.000Z",
+        "username": "nature_lover",
+        "email": "naturelover@example.com",
+        "profilePicture": {"__type": "Pointer", "className": "Image", "objectId": "img2"}
+    },
+    {
+        "objectId": "user3",
+        "createdAt": "2024-01-03T12:00:00.000Z",
+        "username": "foodie_explorer",
+        "email": "foodieexplorer@example.com",
+        "profilePicture": {"__type": "Pointer", "className": "Image", "objectId": "img3"}
+    }
+];
+
+module.exports.PostList = [
+    {
+        "objectId": "post1",
+        "createdAt": "2024-01-05T09:00:00.000Z",
+        "title": "南昌的美食之旅",
+        "content": "南昌的米粉真的非常好吃,推荐大家去尝试!",
+        "location": {"__type": "GeoPoint", "latitude": 28.682, "longitude": 115.857},
+        "privacy": "公开",
+        "user": {"__type": "Pointer", "className": "User", "objectId": "user1"},
+        "tags": [
+            {"__type": "Pointer", "className": "Tag", "objectId": "tag1"},
+            {"__type": "Pointer", "className": "Tag", "objectId": "tag2"}
+        ],
+        "images": [
+            {"__type": "Pointer", "className": "Image", "objectId": "img4"},
+            {"__type": "Pointer", "className": "Image", "objectId": "img5"}
+        ]
+    },
+    {
+        "objectId": "post2",
+        "createdAt": "2024-01-06T10:30:00.000Z",
+        "title": "南昌的文化景点",
+        "content": "推荐大家去八一起义纪念馆,了解南昌的历史。",
+        "location": {"__type": "GeoPoint", "latitude": 28.678, "longitude": 115.866},
+        "privacy": "公开",
+        "user": {"__type": "Pointer", "className": "User", "objectId": "user2"},
+        "tags": [
+            {"__type": "Pointer", "className": "Tag", "objectId": "tag3"}
+        ],
+        "images": [
+            {"__type": "Pointer", "className": "Image", "objectId": "img6"}
+        ]
+    },
+    {
+        "objectId": "post3",
+        "createdAt": "2024-01-07T14:00:00.000Z",
+        "title": "南昌的自然风光",
+        "content": "南昌的滕王阁风景如画,适合拍照留念。",
+        "location": {"__type": "GeoPoint", "latitude": 28.685, "longitude": 115.892},
+        "privacy": "私密",
+        "user": {"__type": "Pointer", "className": "User", "objectId": "user3"},
+        "tags": [
+            {"__type": "Pointer", "className": "Tag", "objectId": "tag4"},
+            {"__type": "Pointer", "className": "Tag", "objectId": "tag5"}
+        ],
+        "images": [
+            {"__type": "Pointer", "className": "Image", "objectId": "img7"},
+            {"__type": "Pointer", "className": "Image", "objectId": "img8"}
+        ]
+    }
+];

+ 58 - 0
travel-server/migration/import-data.js

@@ -0,0 +1,58 @@
+const { CloudQuery, CloudObject } = require("../lib/ncloud");
+const { UserList, PostList } = require("./data");
+inportTravelPostAndTravelUser()
+
+DataMap = {
+    User:{},
+    Post:{}
+}
+
+async function inportTravelPostAndTravelUser(){
+    // 导入用户数据
+    let userList = UserList
+    for (let index = 0; index < userList.length; index++) {
+        let user = userList[index];
+        user = await importObject("User",user)
+    }
+    // 导入帖子数据
+    let postList = PostList
+    for (let index = 0; index < postList.length; index++) {
+        let post = postList[index];
+        post = await importObject("Post",post)
+    }
+    // console.log(DataMap["Post"])
+}
+
+async function importObject(className,data){
+
+    // 查重 srcId 数据源列表中的objectId并非数据库生成的唯一ID,因此需要有一个srcId字段进行记录,并查重
+    let query = new CloudQuery(className)
+    let srcId = data.objectId
+    query.equalTo("srcId",srcId)
+    let importObj = await query.first()
+    console.log(importObj)
+
+    // 导入
+    // 导入前批量处理Pointer类型数据,进行重定向
+    Object.keys(data)?.forEach(key=>{
+        let field = data[key]
+        let srcId = field?.objectId
+        if(srcId){ // 是数组字段
+            if(key=="user"){
+                data[key] = DataMap?.["User"]?.[srcId]?.toPointer();
+            }
+        }
+    })
+
+    // 若未添加,则创建新对象并保存
+    if(!importObj?.id){
+        importObj = new CloudObject(className)
+    }
+
+    // 保存或更新数据
+    data.srcId = srcId;
+    importObj.set(data);
+    importObj = await importObj.save();
+
+    DataMap[className][srcId] = importObj
+}