class_presentation:基于仓颉+Spire+React的全栈博客系统项目

cangjie课堂演示

分支1Tags1
当前项目代码仓暂无内容

Blog

全栈博客系统 — 后端使用仓颉(Cangjie)+ Spire 框架,前端使用 React + TypeScript + Vite

开发方式: 本项目使用 DeepSeek V4 + Claude Code 进行 AI 辅助开发。

项目预览

Blog 首页预览

项目结构

blog/
├── web-api/               # 后端:仓颉 Web API
│   ├── src/
│   │   ├── main.cj        # 入口:WebHost 配置、认证、路由
│   │   ├── controllers/   # 控制器:路由入口,参数转发
│   │   ├── application/
│   │   │   ├── commands/  # 命令(CQRS 写操作):增删改 + Handler
│   │   │   ├── queries/   # 查询(CQRS 读操作):分页列表、详情
│   │   │   ├── services/  # 业务服务:可复用逻辑
│   │   │   ├── models/    # 应用层模型:SearchModel 等
│   │   │   └── behaviors/ # 管道行为:事务等
│   │   ├── domain/
│   │   │   ├── entities/  # 实体:数据库表映射(entity.bat 生成)
│   │   │   ├── models/    # DTO:序列化模型(entity.bat 生成)
│   │   │   └── views/     # 视图实体(entity.bat 生成)
│   │   └── infrastructure/
│   │       ├── AdminDbContext.cj   # 数据库上下文
│   │       ├── MapperUtility.cj    # 对象映射工具
│   │       └── middlewares/        # 中间件(异常处理等)
│   ├── template/          # entity.bat 代码生成模板
│   ├── library/           # 本地依赖(MariaDB 驱动)
│   └── wwwroot/           # 前端构建产物(部署目录)
├── web-ui/                # 前端:React SPA
│   └── src/
│       ├── api/           # API 调用层
│       ├── components/    # 通用组件
│       ├── pages/         # 页面组件
│       ├── stores/        # Zustand 状态管理
│       ├── router/        # 路由配置
│       └── types/         # TypeScript 类型
└── blog.sql               # 数据库初始化脚本

技术栈

后端

技术 说明
仓颉(Cangjie) 编程语言,高性能系统级语言
Spire 框架 SoulSoft Web 全家桶:MVC、DI、认证、序列化、Mediator
SQLSharp ORM 框架,DbContext + DbSet + LINQ 风格查询
MariaDB 关系型数据库
JWT Bearer 身份认证方案

Spire 框架组成:

用途
soulsoft_web_mvc Web 主机、路由、中间件管道
soulsoft_mediator CQRS 中介者:IRequest → IRequestHandler
soulsoft_serialization JSON 序列化/反序列化,@Serialization 注解
soulsoft_web_authentication_jwtbearer JWT Bearer 认证
soulsoft_identity_tokens JWT Token 颁发与验证
soulsoft_extensions_* DI 容器、配置、日志等基础设施

前端

技术 说明
React 18 UI 框架
TypeScript 类型安全
Vite 构建工具 & 开发服务器
Tailwind CSS 原子化 CSS(GitHub Dark 主题)
Zustand 轻量状态管理
React Router 6 客户端路由

快速开始

环境要求

  • 仓颉 SDK(Cangjie 1.1.0+)
  • Node.js 18+
  • MariaDB / MySQL 8.0+
  • sqlsharp_utils — 代码生成工具,需克隆并编译(见步骤 2)

1. 初始化数据库

执行 blog.sql 建库建表:

mysql -u root -p < blog.sql

数据库配置在 web-api/appsettings.Development.json

{
  "ConnectionStrings": {
    "mysql": "mariadb://localhost:3306?username=root&password=1024&database=blog&pool.max.size=100&pool.max.idle.size=100",
    "opengauss": "opengauss://gaussdb:OpenGauss@123@115.120.192.208:5432/cangjie_framework?sslmode=disable"
  }
}

2. 代码生成(DB-First)

下载代码生成工具

首先下载并编译 sqlsharp_utils

git clone https://gitcode.com/soulsoft/sqlsharp_utils.git
cd sqlsharp_utils

# 编译 dbgen 工具
cjpm update
cjpm build

编译完成后将 dbgen 可执行文件放到 PATH 路径下,或复制到 web-api/ 目录。

运行代码生成

数据库表结构变更后,运行 entity.bat 自动生成 Entity / Model / View:

cd web-api
./entity.bat

生成的文件:

  • src/domain/entities/ — 表映射实体
  • src/domain/views/ — 视图实体
  • src/domain/models/ — DTO 模型

3. 启动后端

cd web-api

# 安装依赖
cjpm update

# 编译
cjpm build

# 运行(默认端口 5000,默认 Production 环境)
cjpm run

切换运行环境:

# 方式一:环境变量
export CANGJIE_ENVIRONMENT=Development
cjpm run

# 方式二:命令行参数
cjpm run -- --environment Development

💡 VS Code 用户:.vscode/settings.json 中配置 "terminal.integrated.env.windows": { "CANGJIE_ENVIRONMENT": "Development" },终端打开即自动设置环境变量。

启动后访问 http://localhost:5000

Swagger 文档地址:http://localhost:5000/openapi

4. 启动前端(开发模式)

cd web-ui

# 安装依赖
npm install

# 启动开发服务器(端口 5173)
npm run dev

开发服务器自动代理 /api 到后端 http://localhost:5000

5. 构建前端(生产模式)

cd web-ui
npm run build

构建产物输出到 web-api/wwwroot/,后端直接托管静态文件,单服务部署。

后端架构

分层设计

请求 → Controller → Mediator/Queries → Service → DbContext → Database

本项目遵循 CQRS 风格 读写分离:

读(Query) 写(Command)
入口 Controller Action + Queries 直接调用 Controller Action + Mediator 派发
处理 Queries → DbContext → SQL CommandHandler → Service → DbContext → SQL
返回 PageResult<T> / 实体 void / Bool
管道 走 Mediator 管道(可插事务等 Behavior)

要点:

  • 查询直接调用 Queries,避免 Mediator 开销
  • 命令必须通过 Mediator 派发,支持管道扩展(事务、日志等)
  • Service 只在多 Command/Controller 复用时抽取

参数绑定规则

Controller Action 参数必须正确标注绑定来源:

注解 适用场景 示例
无注解(默认 FromBody) 复杂类型(class,有 @Serialization create(command: CreateCommand)
@FromQuery 基本类型(Int64String)从 URL 取值 getDetail(@FromQuery id: Int64)
@FromForm form-urlencoded 表单(如登录) token(@FromForm username: String)
@FromRoute 从路由路径取值 getBySlug(@FromRoute slug: String)

⚠️ 基本类型(Int64StringBool必须显式标注 @FromQuery@FromForm@FromRoute,否则参数值为默认值(0、空字符串)。

whereIf 筛选语法

// 模糊搜索
.whereIf("POSITION(@keyword IN title) > 0", model.keyword.isSome())

// 精确匹配
.whereIf("category_code = @categoryCode", model.categoryCode.isSome())

// 子查询
.whereIf("id IN (SELECT pt.post_id FROM post_tag pt JOIN tag t ON t.id = pt.tag_id WHERE t.name = @tagName)", model.tagName.isSome())

// 固定条件(始终生效)
.whereIf("is_public = 1")

控制器示例

@Route["api/[controller]/[action]"]
public class BlogController <: Controller {
    public BlogController(let mediator: IMediator, let queries: BlogQueries) {}

    @HttpPost
    public func getList(model: BlogPostSearchModel) {
        return queries.getBlogPostList(model)
    }

    @HttpPost
    public func getDetail(@FromQuery id: Int64) {
        return queries.getBlogPostDetail(id)
    }

    @HttpPost
    public func create(command: CreateBlogPostCommand) {
        mediator.send(command)
    }
}

API 概览

模块 接口 方法 说明
认证 /api/Connect/token POST 登录获取 JWT
认证 /api/Connect/userinfo GET 获取当前用户信息
用户 /api/User/register POST 注册
用户 /api/User/getUserProfile POST 获取用户资料
用户 /api/User/updateProfile POST 编辑资料
用户 /api/User/getUserPosts POST 用户的文章列表
博客 /api/Blog/getBlogPostList POST 文章列表(分页+筛选+排序)
博客 /api/Blog/getBlogPostDetail POST 文章详情
博客 /api/Blog/createBlogPost POST 创建文章
博客 /api/Blog/updateBlogPost POST 更新文章
博客 /api/Blog/deleteBlogPost POST 删除文章
博客 /api/Blog/getComments POST 评论列表
博客 /api/Blog/createComment POST 创建评论
博客 /api/Blog/toggleStar POST 收藏/取消收藏
博客 /api/Blog/toggleFollow POST 关注/取消关注
博客 /api/Blog/getCategories POST 全部分类
博客 /api/Blog/getTags POST 全部标签

前端页面

路由 页面 说明
/ Home 首页:文章列表 + 侧边栏筛选
/login Login 登录
/register Register 注册
/write Write 创建文章
/write/:id Write 编辑文章
/blog/:id BlogDetail 文章详情 + 评论
/profile/:username Profile 用户主页
/settings Settings 个人设置

注意事项

  • 数据库表名用单数(user 而非 users
  • 仓颉关键字(如 key)不能作为字段名,需改列名或使用反引号转义
  • 布尔字段在数据库中为 TINYINT(1),仓颉中映射为 Int8
  • 前端构建到 wwwroot/ 后,后端作为静态文件服务,无需单独部署

项目介绍

cangjie课堂演示

定制我的领域