向全栈迈进——Angular+Tornado开发树洞博客(八)

本文详述了使用Angular和Tornado框架开发故事系统的前后端实现过程,包括创建Angular模块、编写故事的前端表单及后端数据库交互、实现StoryService以及设置路由和菜单。此外,还涉及了数据库表结构设计和故事数据的存储。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一篇博客中,我们实现了用户登录功能,并介绍了如何在angular中使用cookie,以及angular中路由事件的使用。在这期博客中,我们将开始开发我们的核心功能——故事系统与评论系统。
我们的故事系统和评论系统如下图所示:
在这里插入图片描述
故事系统由一个简单的表单组成,包括标题栏和内容栏,内容栏我们会使用之前安装好的ngx-quill富文本框插件来实现,以支持富文本功能。
我们的评论系统如下图所示:
在这里插入图片描述
用户可以对每篇故事发表评论,且可以对故事下的每篇评论再次评论,形成一个评论树。
下面,就让我们看看该如何实现这两个系统吧。

十一 故事系统的开发

1 编写故事

前端部分

我们会把整个故事系统和评论系统作为一个新的模块来开发,因此我们需要建立一个新的angular模块。
在cmd中输入如下命令,让angular为我们建立一个新的模块:

ng g module story

这样,angluar就会为我们建立一个新的story模块,我们可以把这个模块加到app.module.ts文件中:

//...
import { StoryModule } from './story/story.module';
@NgModule({
  declarations: [
  	//...
  ],
  imports: [
	//...
    StoryModule,
  ],
  providers: [CookieService],
  bootstrap: [AppComponent]
})
export class AppModule { }

接下来,让我们在story模块中建立writestory组件,用于编写故事。在cmd中输入以下命令,在story模块中建立writestory组件:

ng g c -m story

-m story表示我们要在story模块中建立这个组件,而不是在根模块下建立。
现在,让我们把所有需要的组件和模块都导入到story模块中,包括之后要在评论系统中使用的模块:

//story.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NzFormModule } from 'ng-zorro-antd/form';
import { NzInputModule } from 'ng-zorro-antd/input';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { NzDividerModule } from 'ng-zorro-antd/divider';
import { NzSpaceModule } from 'ng-zorro-antd/space';
import { NzCardModule } from 'ng-zorro-antd/card';
import { QuillModule } from 'ngx-quill';
import { NzLayoutModule } from 'ng-zorro-antd/layout';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { IconDefinition } from '@ant-design/icons-angular';
import { NzCollapseModule } from 'ng-zorro-antd/collapse';
import { NzCommentModule } from 'ng-zorro-antd/comment';
import { NzAvatarModule } from 'ng-zorro-antd/avatar';
import { StarOutline, CommentOutline, LikeOutline } from '@ant-design/icons-angular/icons';
import { NzListModule } from 'ng-zorro-antd/list';

const icons: IconDefinition[] = [ StarOutline, CommentOutline, LikeOutline ];
@NgModule({
  declarations: [
  ],
  imports: [
    CommonModule,
    NzFormModule,
    NzInputModule,
    NzButtonModule,
    FormsModule,
    ReactiveFormsModule,
    NzDividerModule,
    NzSpaceModule,
    NzCardModule,
    QuillModule.forRoot(),
    NzLayoutModule,
    NzIconModule.forRoot(icons),
    NzCollapseModule,
    NzCommentModule,
    NzAvatarModule,
    NzListModule
    
  ],
  exports:[]
})
export class StoryModule { }

接下来,我们在writestory中新建storyData.ts,定义故事的接口,以便之后和服务器通信使用:

//storyData.ts
export interface storyData {
    author:string;
    title: string;
    content: string;
  }

这个接口很简单,包括作者、标题和内容三部分。
然后,我们打开writestory.component.html,开始编写前端代码:

<form nz-form [formGroup]="storyForm" (ngSubmit)="onSubmit()">
    <nz-form-item>
      <nz-form-control>
        <nz-input-group>
            <p><input nz-input placeholder="标题" formControlName="title"/></p>
        </nz-input-group>
      </nz-form-control>
    </nz-form-item>
    <nz-form-item>
      <nz-form-control>
        <nz-input-group>
          <p><quill-editor formControlName="content"></quill-editor></p>
        </nz-input-group>
      </nz-form-control>
    </nz-form-item>
    <nz-form-item>
      <nz-form-control>
        <button nz-button class="login-form-button" [nzType]="'primary'">提交</button>
      </nz-form-control>
    </nz-form-item>
  </form>

这个页面显示一个简单的表单,包括标题、内容和提交按钮。
然后,让我们打开writestory.component.ts文件,编写前端背后的逻辑代码:

//writestory.component.ts
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import { CookieService } from 'ngx-cookie-service';
import { StoryService } from '../../service/story.service';
import { storyData } from './storyData';

@Component({
  selector: 'app-writestory',
  templateUrl: './writestory.component.html',
  styleUrls: ['./writestory.component.scss']
})
export class WritestoryComponent implements OnInit {
  storyForm = new FormGroup({
    title:new FormControl(''),
    content:new FormControl(''),
  });
  newStory:storyData;
  author:string;
  constructor(private cookie:CookieService,private storyService:StoryService,private router:Router) {
    this.author = this.cookie.get('currentuser')
    this.newStory = {author:this.author,title:'',content:''}
   }

  ngOnInit(): void {
  }

  onSubmit():void{
    this.newStory = this.storyForm.value;
    this.newStory.author = this.author;
    console.log(this.newStory)
    this.storyService.writeStory(this.newStory).subscribe((data:any)=>{
      if (data)
      {
        console.log(data['result']);
        if (data['result'] == 'Success')
        {
          this.router.navigateByUrl('/');
        }
      }
    })
  }

这里的构造函数包含了一个CookieService对象、一个稍后要实现的StoryService对象和一个Router对象。其中CookieService对象用于获得当前登录的用户名,作为故事的作者;StoryService用于与后端服务器通信,而Router则用来发布故事之后的页面跳转操作。在onSubmit函数中,我们会将表单数据通过service传送到后端服务器,如果服务器返回的结果是Success的话,则重定向到根路径。
现在,让我们编写StoryService服务,它将负责与后端服务器通信。
我们在cmd窗口中输入以下命令,建立storyService:

ng g s service/story

然后,在生成的story.service.ts中输入以下代码:

//story.service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { storyData } from '../story/writestory/storyData';

@Injectable({
  providedIn: 'root'
})
export class StoryService {

  constructor(private http:HttpClient) { }

  writeStory(story:storyData):Observable<storyData>{
    return this.http.post<storyData>('http://localhost:8000/writestory',story)
  }
}

这个服务目前只实现了一个功能:writeStory,其用途是将前端的数据以storyData接口的形式传递到后端服务器,并得到结果。
最后,让我们把writestory组件加入到angular的路由以及首页的菜单中,这样我们就可以通过菜单访问这个页面了:

//app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { WritestoryComponent } from './story/writestory/writestory.component';


const routes: Routes = [
  {path:'register',component:RegisterComponent},
  {path:'login',component:LoginComponent},
  {path:'',component:HomeComponent},
  {path:'writestory',component:WritestoryComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

<!--mainlayout.component.html-->
<li nz-menu-item routerLink='/writestory'>书写故事</li>

这样,我们就完成了编写故事的前端部分代码,接下来让我们来看它的后端部分。

后端部分

我们先来处理数据库的部分。我们需要一张新表storys来存储每篇故事,其字段介绍如下:

字段名类型含义
idInteger内部Id,自增
authorstring作者用户名
titlestring标题
contentstring内容
publishdateDate发布日期
commentCountInteger评论数
statestring文章状态
externalIdstring外部Id,即别的数据引用该记录的Id

我们在database目录中建立tblstorys.py文件,输入如下代码:

from database.tablebase import Base
from sqlalchemy import Column,String,Integer,Date,DateTime


class Storys(Base):
    __tablename__ = 'storys'
    id = Column(Integer,autoincrement=True,primary_key=True)
    author = Column(String,nullable=False)
    title = Column(String,nullable=False)
    content = Column(String,nullable=False)
    publishDate = Column(Date)
    commentCount = Column(Integer)
    state = Column(String)
    externalId = Column(String)
    def __repr__(self):
        return '<storys(author=%s,title=%s,publishDate=%s)>' % (self.auther,self.title,self.publishDate)

再在alembic目录下打开cmd,输入如下命令:

alembic revision -m create storys table 

在生成的xxx_create_storys_table.py文件中的upgrade函数中输入以下代码:

# xxx_create_storys_table.py
def upgrade():
    op.create_table(
        'storys',
        sa.Column('id', sa.Integer, primary_key=True, autoincrement=True),
        sa.Column('author', sa.String, nullable=False),
        sa.Column('title', sa.String, nullable=False),
        sa.Column('content', sa.String, nullable=False),
        sa.Column('publishDate', sa.Date),
        sa.Column('commentCount', sa.Integer),
        sa.Column('state', sa.String, nullable=False),
    )

然后再在cmd中执行以下命令,在sqlite数据库中建立storys表:

alembic upgrade head

这样,我们的数据库部分就准备完毕了。
我们在util包下再建立一个story包,再在其中建立storyutil.py文件。我们将在这个文件中实现各种story和comment相关的util函数。
让我们打开storyutil.py文件,实现createStory函数,这个函数将用于编写故事。

# util/story/storyutil.py
from database.dbcore import session
from database.curd import insertdata,deletedata
from database.tblstorys import Storys
from datetime import date,datetime


def createStory(author,title,content):
    newstory = Storys(author=author,title=title,content=content,publishDate=date.today(),commentCount=0,state='WaitForApprove')
    newstory.externalId = 'doc_' + datetime.now().strftime('%Y%m%d%H%M%S').__str__()
    result = insertdata(newstory)
    return result

这个函数很简单,使用传入的author、title和content参数在storys表中生成一行记录。其中,publishDate用今天的时间;commentCount默认为0,而state默认为WaitForApprove,为之后的故事审批系统预留好数据;externalId则是用’doc_'加当前时间组成,以确保唯一性。
我们已经实现了编写故事的util函数,现在让我们去实现相关的RequestHandler。我们在apps包下再建立story_comment_app包,再在其中新建story_comment_app.py文件,我们将在这里实现所有与故事和评论相关的RequestHandler。
我们打开story_comment_app.py文件,输入以下代码:

# apps/story_comment_app/story_comment_app.py
from server.apps.basehandler import BaseHandler
from tornado.escape import json_decode
from database.curd import session
import json
from util.story.storyutil import createStory
from database.tblstorys import Storys

class WriteStory(BaseHandler):
    def post(self):
        ret = self.request.body
        req = json_decode(ret)
        author = req['author']
        title = req['title']
        content = req['content']
        result = createStory(author,title,content)
        returnResult = {'result':result}
        returnResult_str = json.dumps(returnResult)
        self.write(returnResult_str)

WriteStory只需实现post方法即可,目的是从前端获得author、title和content的值,并传入createStory函数来编写故事。
最后,我们在main.py中将WriteStory加入后端路由:

# main.py
# ...
def make_app():
    routelist = [
        (r"/register",Register),
        (r"/login",Login),
        (r"/writestory",WriteStory),
    ]
# ...

这样,我们就完成了编写故事的后端部分的开发。
在这期博客中,我们建立了一个新模块story,并在其中完成了编写故事功能的开发。在下一期博客中,我们将实现一个故事列表,这个故事列表会将所有故事展示在我们项目的首页;我们还会开始开发评论功能,并最终实现评论树的功能,希望大家继续关注~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值