小律书 技术架构详解:前后端分离的自律管理系统设计

小律书 技术架构详解:前后端分离的自律管理系统设计

目录

  • [小律书 技术架构详解:前后端分离的自律管理系统设计](#小律书 技术架构详解:前后端分离的自律管理系统设计)
    • 一、整体架构概览
      • [1.1 系统架构图](#1.1 系统架构图)
      • [1.2 技术栈矩阵](#1.2 技术栈矩阵)
    • 二、前端架构设计
      • [2.1 目录结构与模块化](#2.1 目录结构与模块化)
      • [2.2 网络请求层设计](#2.2 网络请求层设计)
        • [2.2.1 Request.js 核心实现](#2.2.1 Request.js 核心实现)
        • [2.2.2 API 接口层设计](#2.2.2 API 接口层设计)
      • [2.3 应用生命周期管理](#2.3 应用生命周期管理)
        • [2.3.1 App.vue 全局配置](#2.3.1 App.vue 全局配置)
      • [2.4 主题系统设计](#2.4 主题系统设计)
    • 三、后端架构设计
      • [3.1 多模块 Maven 项目结构](#3.1 多模块 Maven 项目结构)
      • [3.2 LazyBones 核心业务模块](#3.2 LazyBones 核心业务模块)
        • [3.2.1 领域模型设计](#3.2.1 领域模型设计)
        • [3.2.2 Controller 层设计](#3.2.2 Controller 层设计)
        • [3.2.3 Service 层设计](#3.2.3 Service 层设计)
      • [3.3 数据库设计](#3.3 数据库设计)
        • [3.3.1 核心表结构](#3.3.1 核心表结构)
      • [3.4 安全认证设计](#3.4 安全认证设计)
        • [3.4.1 Sa-Token 配置](#3.4.1 Sa-Token 配置)
        • [3.4.2 JWT Token 流程](#3.4.2 JWT Token 流程)
    • 四、数据流与通信机制
      • [4.1 完整请求流程](#4.1 完整请求流程)
      • [4.2 跨页面通信](#4.2 跨页面通信)
    • 五、性能优化策略
      • [5.1 前端优化](#5.1 前端优化)
      • [5.2 后端优化](#5.2 后端优化)
    • 六、总结与展望

摘要:本文深入剖析 Inner Citadel(内心堡垒)自律管理系统的完整技术架构,涵盖前后端分层设计、微服务模块划分、数据模型设计、API 接口规范以及跨平台通信机制。通过详细的架构图和代码示例,展示如何构建一个企业级、可扩展的移动应用系统。

一、整体架构概览

1.1 系统架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      用户层 (User Layer)                      │
├─────────────────┬─────────────────┬─────────────────────────┤
│   微信小程序     │     H5 页面      │      Android/iOS APP    │
│  (WeChat Mini)  │   (Mobile Web)  │      (Native/Hybrid)    │
└────────┬────────┴────────┬────────┴──────────┬──────────────┘
         │                 │                    │
         └─────────────────┼────────────────────┘
                           │
                  ┌────────▼────────┐
                  │   UniApp 框架    │
                  │  (跨平台适配层)  │
                  └────────┬────────┘
                           │
         ┌─────────────────┼─────────────────┐
         │                 │                 │
┌────────▼────────┐ ┌─────▼──────┐ ┌────────▼────────┐
│  网络请求层     │ │  状态管理   │ │   UI 组件库      │
│  Request.js    │ │ Vuex/Store │ │  UView Plus     │
└────────┬────────┘ └────────────┘ └─────────────────┘
         │
         │ HTTPS/WebSocket
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│                    API 网关层 (Gateway)                        │
│          Nginx / Spring Cloud Gateway                        │
└────────────────────────┬────────────────────────────────────┘
                         │
         ┌───────────────┼───────────────┐
         │               │               │
┌────────▼────────┐ ┌───▼────────┐ ┌───▼──────────┐
│  认证授权服务    │ │  业务服务   │ │   公共服务    │
│  OAuth Server  │ │ LazyBones  │ │   System     │
└─────────────────┘ └─────┬──────┘ └──────────────┘
                          │
                ┌─────────▼──────────┐
                │   Spring Boot 2.5  │
                │   应用服务层        │
                └─────────┬──────────┘
                          │
         ┌────────────────┼────────────────┐
         │                │                │
┌────────▼────────┐ ┌────▼──────┐ ┌──────▼────────┐
│  MyBatis Plus   │ │  Redis    │ │   PostgreSQL  │
│   ORM 框架       │ │  缓存层    │ │    数据库     │
└─────────────────┘ └───────────┘ └───────────────┘

1.2 技术栈矩阵

层级 技术选型 版本 说明
前端框架 UniApp 3.x 跨平台开发框架
前端语言 Vue.js 3.x Composition API
UI 组件库 UView Plus 3.7+ 移动端组件库
网络请求 uni.request - 封装 Axios 类似物
后端框架 Spring Boot 2.5.14 快速开发框架
ORM 框架 MyBatis Plus 3.5.3.1 数据库操作
数据库 PostgreSQL 12+ 关系型数据库
缓存 Redis 6.x 分布式缓存
认证 Sa-Token 最新 权限认证框架
API 文档 Swagger 3 3.0.0 接口文档生成

二、前端架构设计

2.1 目录结构与模块化

复制代码
inner-citadel-app/
├── common/                      # 公共模块层
│   ├── api/                     # API 接口定义
│   │   ├── auth.js             # 认证接口(登录/注册/微信授权)
│   │   └── lazybones.js        # 业务接口(任务/监督/统计)
│   ├── config.js               # 配置文件(API 基础路径)
│   ├── request.js              # 网络请求封装
│   └── share.js                # 分享功能混入
├── pages/                       # 页面文件层
│   ├── home/                   # 首页(任务看板)
│   ├── task/                   # 任务管理
│   │   ├── create.vue         # 创建任务
│   │   ├── detail.vue         # 任务详情
│   │   ├── records.vue        # 任务清单
│   │   ├── supervised.vue     # 我监督的任务
│   │   ├── assigned.vue       # 指派给我的任务
│   │   └── overdue.vue        # 超时任务
│   ├── auth/                   # 认证模块
│   │   └── login.vue          # 登录/注册页
│   ├── stats/                  # 数据统计
│   ├── profile/                # 个人中心
│   └── settings/               # 设置页面
├── static/                      # 静态资源
│   ├── images/                 # 图片资源
│   └── css/                    # 全局样式
├── App.vue                      # 应用配置(生命周期/主题)
├── main.js                      # 入口文件
├── pages.json                   # 页面路由配置
├── manifest.json                # 应用配置
└── uni.scss                     # 全局样式变量

2.2 网络请求层设计

2.2.1 Request.js 核心实现
javascript 复制代码
// common/request.js
const isAbsoluteUrl = (url) => /^https?:\/\//i.test(String(url || ''));

const joinUrl = (base, path) => {
    const p = String(path || '').trim();
    if (!p) return String(base || '');
    if (isAbsoluteUrl(p)) return p;
    const b = String(base || '').replace(/\/+$/, '');
    if (!b) return p;
    if (p.startsWith(b)) return p;
    if (p.startsWith('/')) return b + p;
    return b + '/' + p;
};

const request = (options) => {
    return new Promise((resolve, reject) => {
        // 1. 获取 Token
        const token = uni.getStorageSync('token');
        
        // 2. 构建请求头
        const header = {
            'Content-Type': 'application/json',
            'Accept': 'application/json, text/plain, */*',
            'Accept-Language': 'zh-CN',
            ...options.header
        };
        
        // 3. JWT Token 注入
        if (token) {
            header['Authorization'] = 'Bearer ' + token;
        }
        
        // 4. URL 拼接
        const fullUrl = joinUrl(baseUrl, options.url);
        const custom = (options.custom || {});
        const timeout = typeof options.timeout === 'number' ? options.timeout : 15000;
        
        // 5. 发起请求
        uni.request({
            url: fullUrl,
            method: options.method || 'GET',
            data: options.data || {},
            header: header,
            timeout,
            success: (res) => {
                // 6. 响应处理
                if (res.statusCode >= 200 && res.statusCode < 300) {
                    const data = res.data;
                    if (data && typeof data === 'object' && data.hasOwnProperty('code')) {
                        if (data.code === 200 || data.code === 0) {
                            resolve(data.hasOwnProperty('rows') ? data : data.data || data);
                        } else {
                            // 业务错误处理
                            if (data.code === 401) {
                                // Token 过期,跳转登录
                                redirectToLogin();
                            }
                            reject(data);
                        }
                    } else {
                        resolve(data);
                    }
                } else if (res.statusCode === 401) {
                    // HTTP 401 未授权
                    redirectToLogin();
                    reject(res.data);
                } else {
                    reject(res.data);
                }
            },
            fail: (err) => {
                console.error('Request failed:', err);
                uni.showToast({ title: '网络请求失败', icon: 'none' });
                reject(err);
            }
        });
    });
};

export default {
    get: (url, data, options = {}) => request({ url, method: 'GET', data, ...options }),
    post: (url, data, options = {}) => request({ url, method: 'POST', data, ...options }),
    put: (url, data, options = {}) => request({ url, method: 'PUT', data, ...options }),
    delete: (url, data, options = {}) => request({ url, method: 'DELETE', data, ...options }),
};
2.2.2 API 接口层设计

采用 Repository 模式,将 API 按业务模块分类:

javascript 复制代码
// common/api/lazybones.js
import request from '../request.js';

// ========== 任务管理 ==========
export const createTask = (data) => 
    request.post('/api/lazybones/task/create', data);

export const updateTask = (data) => 
    request.put('/api/lazybones/task/update', data);

export const deleteTask = (id) => 
    request.delete(`/api/lazybones/task/delete/${encodeURIComponent(id)}`);

export const getTaskDetail = (id, options) => 
    request.get(`/api/lazybones/task/detail/${encodeURIComponent(id)}`, null, options);

export const listTasks = (params, options) => 
    request.get('/api/lazybones/task/list', params, options);

export const listActiveTasks = (options) => 
    request.get('/api/lazybones/task/active', null, options);

// ========== 任务分配 ==========
export const listSupervisedTasks = (options) => 
    request.get('/api/lazybones/task/supervised', null, options);

export const listAssignedTasks = (options) => 
    request.get('/api/lazybones/task/assigned', null, options);

export const acceptTask = (id) => 
    request.post(`/api/lazybones/task/${encodeURIComponent(id)}/accept`);

export const rejectTask = (id) => 
    request.post(`/api/lazybones/task/${encodeURIComponent(id)}/reject`);

// ========== 提醒功能 ==========
export const remindTask = (id, data) => 
    request.post(`/api/lazybones/task/${encodeURIComponent(id)}/remind`, data);

export const getTaskRemindSummary = (id, options) => 
    request.get(`/api/lazybones/task/${encodeURIComponent(id)}/remind/summary`, null, options);

// ========== 监督人管理 ==========
export const addSupervisor = (data) => 
    request.post('/api/lazybones/supervisor/add', data);

export const removeSupervisor = (id) => 
    request.delete(`/api/lazybones/supervisor/remove/${encodeURIComponent(id)}`);

export const listSupervisors = (options) => 
    request.get('/api/lazybones/supervisor/list', null, options);

// ========== 违规记录 ==========
export const createViolation = (data) => 
    request.post('/api/lazybones/violation/create', data);

export const listViolations = (options) => 
    request.get('/api/lazybones/violation/list', null, options);

// ========== 统计数据 ==========
export const getUserStats = (options) => 
    request.get('/api/lazybones/user/stats', null, options);

export const getTaskStats = (params, options) => 
    request.get('/api/lazybones/task/stats', params, options);

2.3 应用生命周期管理

2.3.1 App.vue 全局配置
vue 复制代码
<script>
import { getLoginUserInfo, wechatLogin } from './common/api/auth.js';

export default {
    onLaunch: async function() {
        // 1. 应用启动时初始化主题
        this.applyTheme();
        
        // 2. 监听全局主题切换事件
        uni.$on('theme:changed', this.handleGlobalThemeChange);
        
        // 3. 监听系统暗黑模式变化(H5 端)
        if (typeof window !== 'undefined' && window.matchMedia) {
            this.__sysDarkMM = window.matchMedia('(prefers-color-scheme: dark)');
            this.__sysDarkMM.addEventListener('change', () => {
                const pref = uni.getStorageSync('modePreference') || '';
                if (pref === 'auto') this.applyTheme();
            });
        }
        
        // 4. 确保登录状态
        await this.ensureLogin();
    },
    
    onShow: async function() {
        // 每次显示时刷新主题和登录状态
        this.applyTheme();
        await this.ensureLogin();
    },
    
    onHide: function() {
        console.log('App Hide');
    },
    
    methods: {
        // 智能登录逻辑
        async ensureLogin() {
            try {
                let token = uni.getStorageSync('token') || '';
                
                if (token) {
                    // 验证 Token 有效性
                    try {
                        const userInfo = await getLoginUserInfo({ 
                            custom: { loginRedirect: false } 
                        });
                        if (userInfo) {
                            uni.setStorageSync('loginUserInfo', userInfo);
                            uni.setStorageSync('userInfo', userInfo);
                        }
                        return;
                    } catch (e) {
                        // Token 失效,清除缓存
                        uni.removeStorageSync('token');
                        uni.removeStorageSync('userInfo');
                        token = '';
                    }
                }
                
                // 微信静默登录
                if (typeof uni.login === 'function') {
                    const code = await new Promise((resolve) => {
                        uni.login({
                            success: (res) => resolve(res.code),
                            fail: () => resolve('')
                        });
                    });
                    
                    if (code) {
                        const ret = await wechatLogin({ code });
                        if (ret && ret.ok) {
                            const auth = ret.data || {};
                            const token = auth.token || auth.userInfo?.accessToken;
                            if (token) {
                                uni.setStorageSync('token', token);
                                uni.setStorageSync('wechatAuth', auth);
                                
                                // 获取用户信息
                                const userInfo = await getLoginUserInfo({ 
                                    custom: { loginRedirect: false } 
                                });
                                if (userInfo) {
                                    uni.setStorageSync('loginUserInfo', userInfo);
                                    uni.setStorageSync('userInfo', userInfo);
                                }
                            }
                        }
                    }
                }
            } catch (e) {
                console.error('Auto-login failed:', e);
            }
        },
        
        // 主题应用逻辑
        applyTheme(theme) {
            let t = theme || '';
            const pref = uni.getStorageSync('modePreference') || '';
            
            if (pref === 'auto') {
                t = this.getSystemPreferredTheme();
            } else if (pref === 'day') {
                t = 'light';
            } else if (pref === 'night') {
                t = 'dark';
            } else {
                t = t || uni.getStorageSync('theme') || 'light';
            }
            
            // 设置根元素类名
            this.setRootThemeClass(t);
            // 更新 TabBar 样式
            this.updateTabBarByTheme(t);
            // 触发全局事件
            uni.$emit('theme:changed', t);
        }
    }
}
</script>

2.4 主题系统设计

支持 8 种预设主题的动态切换系统:

scss 复制代码
// App.vue 样式部分

/* ========== 主题基色定义 ========== */
.theme-light page { 
    background-color: #F6F7F9; 
    color: #0B1220; 
}

.theme-dark page { 
    background-color: #F0F2F5; 
    color: #001529; 
}

.theme-neon page { 
    background-color: #0A0F1A; 
    color: #E6F7FF; 
}

.theme-nord page { 
    background-color: #2E3440; 
    color: #ECEFF4; 
}

.theme-paper page { 
    background-color: #FAFAFC; 
    color: #0B1220; 
}

.theme-mint page { 
    background-color: #F4FBF7; 
    color: #0B1220; 
}

.theme-sepia page { 
    background-color: #F7F1E3; 
    color: #3E3A33; 
}

.theme-flow page { 
    background-color: #F0F2F5; 
    color: #0B1220; 
}

/* ========== 主题变量映射 ========== */
.theme-light .bg-card { background-color: #FFFFFF; }
.theme-light .text-white { color: #0B1220; }
.theme-light .border-slate-700 { border: 1px solid #E5E7EB; }

.theme-dark .bg-card { background-color: #FFFFFF; }
.theme-dark .text-white { color: #001529; }
.theme-dark .border-slate-700 { border: 1px solid #e2e8f0; }

/* ========== TabBar 主题适配 ========== */
updateTabBarByTheme(t) {
    const themeMap = {
        dark: { 
            backgroundColor: '#FFFFFF', 
            selectedColor: '#10b981' 
        },
        light: { 
            backgroundColor: '#FFFFFF', 
            selectedColor: '#10b981' 
        },
        neon: { 
            backgroundColor: '#FFFFFF', 
            selectedColor: '#22D3EE' 
        }
    };
    
    const s = themeMap[t] || themeMap.dark;
    uni.setTabBarStyle({
        backgroundColor: s.backgroundColor,
        selectedColor: s.selectedColor
    });
}

三、后端架构设计

3.1 多模块 Maven 项目结构

复制代码
huimayun-boot/
├── huimayun-webservice/           # Web 服务启动模块
│   └── src/main/
│       ├── java/com/huimayun/    # 启动类
│       └── resources/            # 配置文件
│           ├── application.yml   # 主配置
│           └── application-dev.yml # 开发环境
├── huimayun-common/               # 通用工具模块
│   └── src/main/java/com/huimayun/common/
│       ├── annotation/           # 自定义注解
│       ├── config/               # 配置类
│       ├── constant/             # 常量定义
│       ├── core/                 # 核心工具
│       ├── enums/                # 枚举类
│       ├── exception/            # 异常处理
│       ├── filter/               # 过滤器
│       ├── utils/                # 工具类
│       └── web/                  # Web 相关
├── huimayun-framework/            # 核心框架模块
│   └── src/main/java/com/huimayun/framework/
│       ├── config/               # 框架配置
│       ├── datasource/           # 数据源配置
│       ├── redis/                # Redis 配置
│       └── web/                  # Web 配置
├── huimayun-boot-system/          # 系统管理模块
│   └── src/main/
│       ├── java/com/huimayun/   # 系统业务代码
│       └── resources/mybatis/   # MyBatis XML
├── huimayun-boot-module/          # 业务模块聚合
│   ├── huimayun-module-lazybones/ # 懒虫管理(核心业务)
│   ├── huimayun-module-asset/    # 资产管理
│   ├── huimayun-module-campus/   # 校园卡管理
│   └── huimayun-module-quartz/   # 定时任务
└── huimayun-boot-oauth/           # OAuth 认证模块

3.2 LazyBones 核心业务模块

3.2.1 领域模型设计
java 复制代码
// LbTask.java - 任务实体
package com.huimayun.lazybones.domain;

import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

@Data
public class LbTask implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // 基础信息
    private String id;                    // 任务 ID(UUID)
    private String userId;                // 创建者 ID
    private String title;                 // 任务标题
    private String description;           // 任务描述
    
    // 时间管理
    private LocalDateTime deadline;       // 截止时间
    private Integer defconLevel;          // 优先级等级 (1-4)
    
    // 进度管理
    private Integer progress;             // 进度百分比 (0-100)
    private String status;                // ACTIVE, COMPLETED, FAILED, DELAYED
    
    // 任务类型
    private String type;                  // survival/work/word:multi/single
    private String contractTerms;         // 契约条款
    private String punishmentConfig;      // 惩罚配置
    
    // 社交属性
    private String supervisorIds;         // 监督人 ID 列表
    private String visibility;            // PUBLIC, PRIVATE
    private String assigneeIds;           // 被指派人 ID 列表
    private String assignStatus;          // PENDING, ACCEPTED, REJECTED
    
    // 审计字段
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    // 扩展信息(VO 字段)
    private String creatorName;
    private String creatorPhone;
}
java 复制代码
// LbSupervisor.java - 监督人实体
package com.huimayun.lazybones.domain;

import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

@Data
public class LbSupervisor implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String id;
    private String userId;                // 任务创建者 ID
    private String supervisorId;          // 监督人 ID
    private String supervisorName;        // 监督人姓名
    private String supervisorPhone;       // 监督人手机号
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}
java 复制代码
// LbViolationLog.java - 违规记录
package com.huimayun.lazybones.domain;

import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

@Data
public class LbViolationLog implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String id;
    private String taskId;                // 关联任务 ID
    private String userId;                // 用户 ID
    private String violationType;         // 违规类型
    private String description;           // 描述
    private LocalDateTime violationTime;  // 违规时间
    private LocalDateTime createdAt;
}
3.2.2 Controller 层设计

采用 RESTful 风格,统一响应格式:

java 复制代码
// LbTaskController.java
package com.huimayun.lazybones.controller;

import com.huimayun.common.utils.SecurityUtils;
import com.huimayun.common.web.domain.UserInfoVo;
import com.huimayun.lazybones.domain.LbTask;
import com.huimayun.lazybones.service.LbTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/lazybones/task")
public class LbTaskController {
    
    @Autowired
    private LbTaskService taskService;
    
    /**
     * 创建任务
     * POST /api/lazybones/task/create
     */
    @PostMapping("/create")
    public ResponseEntity<LbTask> createTask(@RequestBody LbTask task) {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        task.setUserId(userId);
        return ResponseEntity.ok(taskService.createTask(task));
    }
    
    /**
     * 更新任务
     * PUT /api/lazybones/task/update
     */
    @PutMapping("/update")
    public ResponseEntity<LbTask> updateTask(@RequestBody LbTask task) {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        task.setUserId(userId);
        return ResponseEntity.ok(taskService.updateTask(task));
    }
    
    /**
     * 删除任务
     * DELETE /api/lazybones/task/delete/{id}
     */
    @DeleteMapping("/delete/{id}")
    public ResponseEntity<Void> deleteTask(@PathVariable String id) {
        taskService.deleteTask(id);
        return ResponseEntity.ok().build();
    }
    
    /**
     * 获取任务详情
     * GET /api/lazybones/task/detail/{id}
     */
    @GetMapping("/detail/{id}")
    public ResponseEntity<LbTask> getTask(@PathVariable String id) {
        return ResponseEntity.ok(taskService.getTaskById(id));
    }
    
    /**
     * 获取所有任务列表
     * GET /api/lazybones/task/list
     */
    @GetMapping("/list")
    public ResponseEntity<List<LbTask>> getTasks() {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        return ResponseEntity.ok(taskService.getTasksByUserId(userId));
    }
    
    /**
     * 获取进行中的任务列表
     * GET /api/lazybones/task/active
     */
    @GetMapping("/active")
    public ResponseEntity<List<LbTask>> getActiveTasks() {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        return ResponseEntity.ok(taskService.getActiveTasks(userId));
    }
    
    /**
     * 获取我监督的任务
     * GET /api/lazybones/task/supervised
     */
    @GetMapping("/supervised")
    public ResponseEntity<List<LbTask>> getSupervisedTasks() {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        return ResponseEntity.ok(taskService.getSupervisedTasks(userId));
    }
    
    /**
     * 获取指派给我的任务
     * GET /api/lazybones/task/assigned
     */
    @GetMapping("/assigned")
    public ResponseEntity<List<LbTask>> getAssignedTasks() {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        return ResponseEntity.ok(taskService.getAssignedTasks(userId));
    }
    
    /**
     * 接受任务
     * POST /api/lazybones/task/{id}/accept
     */
    @PostMapping("/{id}/accept")
    public ResponseEntity<Void> acceptTask(@PathVariable String id) {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        taskService.acceptTask(id, userId);
        return ResponseEntity.ok().build();
    }
    
    /**
     * 拒绝任务
     * POST /api/lazybones/task/{id}/reject
     */
    @PostMapping("/{id}/reject")
    public ResponseEntity<Void> rejectTask(@PathVariable String id) {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        taskService.rejectTask(id, userId);
        return ResponseEntity.ok().build();
    }
    
    /**
     * 提醒任务
     * POST /api/lazybones/task/{id}/remind
     */
    @PostMapping("/{id}/remind")
    public ResponseEntity<Void> remindTask(
            @PathVariable String id,
            @RequestBody(required = false) Map<String, Object> body) {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        String remindType = body != null && body.get("type") != null 
            ? String.valueOf(body.get("type")).trim() 
            : "durian";
        taskService.remindTask(id, userId, remindType);
        return ResponseEntity.ok().build();
    }
    
    /**
     * 获取任务统计数据
     * GET /api/lazybones/task/stats
     */
    @GetMapping("/stats")
    public ResponseEntity<LbTaskStatsVo> getTaskStats(
            @RequestParam(required = false) String range) {
        String userId = getLoginUserId();
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        return ResponseEntity.ok(taskService.getTaskStats(userId, range));
    }
    
    /**
     * 获取当前登录用户 ID
     */
    private String getLoginUserId() {
        UserInfoVo user = SecurityUtils.getLoginUser();
        if (user == null) return null;
        String id = user.getId();
        if (id == null || id.trim().isEmpty()) return null;
        return id.trim();
    }
}
3.2.3 Service 层设计
java 复制代码
// LbTaskService.java - 业务逻辑接口
package com.huimayun.lazybones.service;

import com.huimayun.lazybones.domain.LbTask;
import com.huimayun.lazybones.domain.LbTaskRemindSummary;
import com.huimayun.lazybones.domain.LbTaskStatsVo;

import java.util.List;

public interface LbTaskService {
    
    /**
     * 创建任务
     */
    LbTask createTask(LbTask task);
    
    /**
     * 更新任务
     */
    LbTask updateTask(LbTask task);
    
    /**
     * 删除任务
     */
    void deleteTask(String id);
    
    /**
     * 根据 ID 获取任务
     */
    LbTask getTaskById(String id);
    
    /**
     * 根据用户 ID 获取任务列表
     */
    List<LbTask> getTasksByUserId(String userId);
    
    /**
     * 获取进行中的任务
     */
    List<LbTask> getActiveTasks(String userId);
    
    /**
     * 获取我监督的任务
     */
    List<LbTask> getSupervisedTasks(String userId);
    
    /**
     * 获取指派给我的任务
     */
    List<LbTask> getAssignedTasks(String userId);
    
    /**
     * 接受任务
     */
    void acceptTask(String id, String userId);
    
    /**
     * 拒绝任务
     */
    void rejectTask(String id, String userId);
    
    /**
     * 提醒任务
     */
    void remindTask(String id, String userId, String remindType);
    
    /**
     * 获取提醒汇总
     */
    LbTaskRemindSummary getRemindSummary(String id, String userId);
    
    /**
     * 获取任务统计数据
     */
    LbTaskStatsVo getTaskStats(String userId, String range);
}

3.3 数据库设计

3.3.1 核心表结构
sql 复制代码
-- 任务表
CREATE TABLE lb_task (
    id VARCHAR(64) PRIMARY KEY,
    user_id VARCHAR(64) NOT NULL COMMENT '创建者 ID',
    title VARCHAR(200) NOT NULL COMMENT '任务标题',
    description TEXT COMMENT '任务描述',
    deadline TIMESTAMP NOT NULL COMMENT '截止时间',
    defcon_level INTEGER DEFAULT 4 COMMENT '优先级 (1-4)',
    progress INTEGER DEFAULT 0 COMMENT '进度百分比',
    status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态',
    type VARCHAR(50) COMMENT '任务类型',
    contract_terms TEXT COMMENT '契约条款',
    punishment_config TEXT COMMENT '惩罚配置',
    supervisor_ids TEXT COMMENT '监督人 ID 列表',
    visibility VARCHAR(20) DEFAULT 'PRIVATE' COMMENT '可见性',
    assignee_ids TEXT COMMENT '被指派人 ID 列表',
    assign_status VARCHAR(20) DEFAULT 'PENDING' COMMENT '指派状态',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_user_id (user_id),
    INDEX idx_status (status),
    INDEX idx_deadline (deadline)
);

-- 监督人表
CREATE TABLE lb_supervisor (
    id VARCHAR(64) PRIMARY KEY,
    user_id VARCHAR(64) NOT NULL COMMENT '用户 ID',
    supervisor_id VARCHAR(64) NOT NULL COMMENT '监督人 ID',
    supervisor_name VARCHAR(100) COMMENT '监督人姓名',
    supervisor_phone VARCHAR(20) COMMENT '监督人手机号',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_user_id (user_id),
    INDEX idx_supervisor_id (supervisor_id)
);

-- 违规记录表
CREATE TABLE lb_violation_log (
    id VARCHAR(64) PRIMARY KEY,
    task_id VARCHAR(64) NOT NULL COMMENT '任务 ID',
    user_id VARCHAR(64) NOT NULL COMMENT '用户 ID',
    violation_type VARCHAR(50) COMMENT '违规类型',
    description TEXT COMMENT '描述',
    violation_time TIMESTAMP COMMENT '违规时间',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_task_id (task_id),
    INDEX idx_user_id (user_id)
);

-- 任务提醒表
CREATE TABLE lb_task_remind (
    id VARCHAR(64) PRIMARY KEY,
    task_id VARCHAR(64) NOT NULL COMMENT '任务 ID',
    user_id VARCHAR(64) NOT NULL COMMENT '用户 ID',
    remind_type VARCHAR(50) COMMENT '提醒类型',
    remind_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '提醒时间',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_task_id (task_id),
    INDEX idx_user_id (user_id)
);

3.4 安全认证设计

3.4.1 Sa-Token 配置
yaml 复制代码
# application.yml
sa-token:
  # token 名称
  token-name: Authorization
  # token 前缀
  token-prefix: Bearer
  # 是否从 cookie 读取
  is-read-cookie: false
  # token 有效期(30 天)
  timeout: 2592000
  # 最低活跃频率(不限制)
  active-timeout: -1
  # 允许多地同时登录
  is-concurrent: true
  # 共享 token
  is-share: true
  # token 风格
  token-style: uuid
  # 输出操作日志
  is-log: true
  
  # OAuth2 配置
  oauth2:
    is-code: true      # 授权码模式
    is-implicit: false # 关闭隐式模式
    is-password: true  # 密码模式
    is-client: true    # 客户端模式
3.4.2 JWT Token 流程
复制代码
┌──────────┐
│   用户    │
└────┬─────┘
     │ 1. 登录请求(用户名密码/微信 code)
     ▼
┌─────────────────┐
│  AuthController │
└────┬────────────┘
     │ 2. 验证凭证
     ▼
┌─────────────────┐
│  UserService    │
└────┬────────────┘
     │ 3. 生成 Token
     ▼
┌─────────────────┐
│  StpUtil.login()│
└────┬────────────┘
     │ 4. 返回 Token
     ▼
┌──────────┐
│   用户    │─── 后续请求携带 Bearer Token
└──────────┘

四、数据流与通信机制

4.1 完整请求流程

复制代码
用户操作 → UniApp → uni.request → Request.js → API 层
                                              ↓
                                        HTTPS 请求
                                              ↓
                                    Nginx 反向代理
                                              ↓
                              Spring Boot Controller
                                              ↓
                                 Service 业务逻辑
                                              ↓
                               MyBatis Plus Mapper
                                              ↓
                                  PostgreSQL/Redis
                                              ↓
                                    返回 JSON 数据
                                              ↓
                                    Request.js 处理
                                              ↓
                                      更新 UI 状态

4.2 跨页面通信

使用 UniApp 的事件总线实现跨页面通信:

javascript 复制代码
// App.vue - 主题切换事件广播
applyTheme(theme) {
    // ...
    uni.$emit('theme:changed', theme);
}

// pages/home/index.vue - 监听主题变化
export default {
    onLoad() {
        uni.$on('theme:changed', this.handleThemeChange);
    },
    onUnload() {
        uni.$off('theme:changed', this.handleThemeChange);
    },
    methods: {
        handleThemeChange(theme) {
            this.theme = theme;
        }
    }
}

五、性能优化策略

5.1 前端优化

  1. 分包加载
json 复制代码
// manifest.json
"mp-weixin": {
    "optimization": {
        "subPackages": true
    }
}
  1. 定时器管理
javascript 复制代码
// 避免内存泄漏
onShow() {
    this.startTicker();
}

onHide() {
    this.stopTicker();
}
  1. 条件渲染优化
vue 复制代码
<view v-if="!hasActiveTasks">
    <!-- 空状态 -->
</view>
<view v-else>
    <!-- 任务列表 -->
</view>

5.2 后端优化

  1. Redis 缓存
java 复制代码
// 热点数据缓存
@Cacheable(value = "task::stats", key = "#userId + '::' + #range")
public LbTaskStatsVo getTaskStats(String userId, String range) {
    return taskMapper.selectStats(userId, range);
}
  1. 数据库索引
sql 复制代码
-- 常用查询字段建立索引
CREATE INDEX idx_task_user_status ON lb_task(user_id, status);
CREATE INDEX idx_task_deadline ON lb_task(deadline);
  1. 分页查询
java 复制代码
// PageHelper 分页
PageHelper.startPage(pageNum, pageSize);
List<LbTask> tasks = taskMapper.selectByUserId(userId);
PageInfo<LbTask> pageInfo = new PageInfo<>(tasks);

六、总结与展望

Inner Citadel 项目展示了一个完整的前后端分离架构设计,具有以下特点:

  1. 跨平台适配:UniApp 实现一套代码多端运行
  2. 模块化设计:Maven 多模块清晰划分业务边界
  3. 安全性:JWT + Sa-Token 双重保障
  4. 可扩展性:MyBatis Plus 简化数据操作
  5. 用户体验:8 种主题、流畅动画、实时反馈

未来改进方向:

  • 引入 WebSocket 实现实时推送
  • 集成 ECharts 增强数据可视化
  • 添加 AI 智能推荐算法
  • 支持离线模式和 PWA

作者简介:资深全栈架构师,专注于企业级应用架构设计与性能优化。


相关推荐
华科易迅2 小时前
Spring AOP(XML最终+环绕通知)
xml·java·spring
IT观测2 小时前
深度分析俩款主流移动统计工具Appvue和openinstall
android·java·数据库
华科易迅2 小时前
Spring AOP(注解前置+后置通知)
java·后端·spring
无忧智库2 小时前
高校数字化转型的范式跃迁:从“单点智能”到“全域协同”的智慧校园新基座(PPT)
架构
堕2742 小时前
JavaEE初阶——《计算机是如何工作的》
java·java-ee
balmtv2 小时前
GPT vs Gemini 架构硬核对决:MoE路由、KV缓存与长上下文推理工程实现深度
gpt·缓存·架构
0xDevNull2 小时前
Apache RocketMQ 完全指南
java·rocketmq
XiaoLeisj2 小时前
Android 文件存储实战:从应用私有目录读写到网络文件落盘与公共存储接入
android·java·网络·文件操作
茶本无香2 小时前
JVM调优介绍 + 面试题标准答案(高级)
java·jvm·面试