湘潭大学新生匿名问答网站——解湘 项目总结

湘潭大学新生匿名问答网站——解湘 项目总结

一.开发进度

温馨提示:左下角有音乐播放器

解湘

56nFX.jpg

项目首页

kOMOK.png

大一暑假过半,7月29日建立本地工程文件

kO8Lk.png

其中项目在github上经历七次push(第八次为修改配置文件,防止数据库泄露),但在本地修改次数远远大于七次。

仓库地址Appletree24/Sky31Welcome (github.com)

后端开发均为我一人完成,前端开发由他人负责。除此之外,特感谢三翼设计部门设计出此次项目的UI界面

kjOMI.png

实际应用接口数量为31个,采用APIFOX进行团队接口管理。部分后端实现接口实际因前端功能不需没有加入。

二.项目大纲

此项目立项起,我就很明确这是个传统的论坛类项目,砍去一些如上传图片用户信息修改等不必要功能。这是一个很好的练习基于Java进行Web开发的机会,不计所谓回报,我投身到了这次的开发中。

kj6Sm.png

项目技术栈总览如上图所示。

其中因技术不牢固原因,Spring Security框架最终并未能加入项目进行权限管理。考虑使用Shiro,但因假期繁忙,最终放弃。

其中邮件验证功能因避免网站使用的复杂性,最终删去。

kjhN7.png

C/S之间使用Nginx进行反向代理,因图片功能删去,未配置CDN加速。

三.项目中遇到的问题&收获

1.Base64编码后长度必须为4的倍数

​ 起因为项目采用Base64对用户密码进行加盐处理,但因方式不当。导致服务端频繁报错,无法正常使用。

2.Mybatis-plus导致Mysql数据库主键出现负数

​ 测试期间发现ORM框架生成的数据在Mysql中出现了id为负数的情况。如下图所示

kjCMi.png

​ MP-plus有五种主键ID生成策略:

  • AUTO,配合数据库设置自增主键,可以实现主键的自动增长,类型为nmber;

  • INPUT,由用户输入;

  • NONE,不设置,等同于INPUT;

  • ASSIGN_ID,只有当用户未输入时,采用雪花算法生成一个适用于分布式环境的全局唯一主键,类型可以是String和number;

  • ASSIGN_UUID,只有当用户未输入时,生成一个String类型的主键,但不保证全局唯一;

​ 其中默认为ASSIGN_ID,即使用雪花算法进行生成。这也就导致对应生成的主键应该使用Long类型进行存储,且设置主键类型为bigint。也可在Java代码中设置@TableId注解

3.跨域问题

​ 老生长谈的一个问题了。本次跨域代码如下:

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //添加映射路径
        registry.addMapping("/**")
                //是否发送Cookie
                .allowCredentials(true)
                //设置放行哪些原始域   SpringBoot2.4.4下低版本使用.allowedOrigins("*")
                .allowedOriginPatterns("*")
                //放行哪些请求方式
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                //.allowedMethods("*") //或者放行全部
                //放行哪些原始请求头部信息
                .allowedHeaders("*")
                //暴露哪些原始请求头部信息
                .exposedHeaders("*");
    }
}

​ 如此常见的一个问题,并不可能在开发前后端分离的项目时未进行考虑。但本次遇到的问题是,发现前端也要在vite中配置有关跨域的内容才能使cookie正常出现。

4.响应头中set-cookie字段

  1. set-cookie是一个函数,由服务器向浏览器发出响应
  2. cookie是服务器发送给浏览器的变量
  3. 浏览器向服务器发送请求(put,get,post,delect方法),服务器会使用set-cookie()方法向本地的浏览器发送cookie,存在客户端主机中的一个文件下

set-cookie响应头可以设置如下属性:

属性 意义
NAME=VALUE 赋予 Cookie 的名称和其值(必需项)
expires=DATE Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止)
path=PATH 将服务器上的文件目录作为Cookie的适用对象(若不指定则默认为文档所在的文件目录)
domain=域名 作为 Cookie 适用对象的域名 (若不指定则默认为创建 Cookie的服务器的域名)
Secure 仅在 HTTPS 安全通信时才会发送 Cookie
HttpOnly 加以限制, 使 Cookie 不能被 JavaScript 脚本访问

max-age
设置cookie的相对有效期。expire和max-age通常仅设置一个即可。比如设置max-age为1000,浏览器在添加cookie时,会自动设置它的expire为当前时间加上1000秒,作为过期时间。
如果不设置expire,又没有设置max-age,则表示会话结束后过期。
对于大部分浏览器而言,关闭所有浏览器窗口意味着会话结束。

5.用户无法发送emoji表情

​ Mysql中的utf8支持一个字符最多3个字节,但emoji表情为4个字节,所以需要切换为utf8mb4即可

6.敏感词过滤器

​ 使用前缀树算法实现敏感词过滤器。

@Component
public class SensitiveFilter {

    private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);

    private static final String REPLACEMENT = "***";

    private TrieNode rootNode = new TrieNode();

    @PostConstruct
    public void init() {
        try (
                InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String keyword;
            while ((keyword = reader.readLine()) != null) {
                this.addKeyWord(keyword);
            }
        } catch (IOException e) {
            logger.error("敏感词加载失败: " + e.getMessage());
        }
    }


    public String filter(String text) {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        TrieNode tempNode = rootNode;
        int begin = 0;
        int position = 0;
        StringBuilder sb = new StringBuilder();
        while (begin < text.length()) {
            char c = text.charAt(position);
            if (isSymbol(c)) {
                if (tempNode == rootNode) {
                    sb.append(c);
                    begin++;
                }
                position++;
                continue;
            }
            tempNode = tempNode.getSubNode(c);
            if (tempNode == null) {
                sb.append(text.charAt(begin));
                position = ++begin;
                tempNode = rootNode;
            } else if (tempNode.isEnd()) {
                sb.append(REPLACEMENT);
                begin = ++position;
                tempNode = rootNode;
            } else {
                if (position < text.length() - 1) {
                    position++;
                } else {
                    sb.append(text.charAt(begin));
                    position = ++begin;
                    tempNode = rootNode;
                }
            }
        }
        sb.append(text.substring(begin));
        return sb.toString();
    }

    private boolean isSymbol(Character c) {
        return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
    }

    private void addKeyWord(String keyword) {
        TrieNode tempNode = rootNode;
        for (int i = 0; i < keyword.length(); i++) {
            char c = keyword.charAt(i);
            TrieNode subNode = tempNode.getSubNode(c);
            if (subNode == null) {
                subNode = new TrieNode();
                tempNode.addSubNode(c, subNode);
            }
            tempNode = subNode;
            if (i == keyword.length() - 1) {
                tempNode.setEnd(true);
            }
        }
    }


    private class TrieNode {
        private boolean isEnd = false;

        private Map<Character, TrieNode> setNodes = new HashMap<>();

        public boolean isEnd() {
            return isEnd;
        }

        public void setEnd(boolean end) {
            isEnd = end;
        }

        public void addSubNode(Character c, TrieNode node) {
            setNodes.put(c, node);
        }

        public TrieNode getSubNode(Character c) {
            return setNodes.get(c);
        }

    }
}

7.切实使用如Redis、Kafka等技术

​ 此前只是学习过Redis、Kafka等技术,但离专业课太远,而又没有合适的项目,于是乎一直悬浮在空中,对技术一知半解。

​ 此项目中的点赞功能、查看个人信息功能、登陆凭证存储功能、消息提醒功能均使用了Redis,也即当对象需要被频繁的访问时,我们可以使用Redis解决问题。

​ 此项目中的消息提醒使用Kafka消息队列进行开发,在学习Kafka的过程中,再次巩固了设计模式中的发布-订阅模式。在此抛出一个疑问,能否使用定期查询数据库的形式来实现消息提醒呢?

8.SSL证书配置

​ 如果要使用HTTPS,那么需要配置SSL证书,在腾讯云中可白嫖免费的证书。此次采用nginx形式进行配置,只需在nginx对应配置文件中加入固定内容,并将证书文件放在置顶目录下即可,十分简单。

server {
    listen       443 ssl;
    server_name  question.tsky31.cn;
    ssl_certificate question.tsky31.cn_bundle.crt;
    ssl_certificate_key question.tsky31.cn.key;
    ssl_session_timeout 5m;
    #请按照以下协议配置
    ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2; 
    #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;

    client_max_body_size    1000m;
}

server {
    listen 80;
    server_name question.tsky31.cn; 
    return 301 https://$host$request_uri; 
}

9.域名配置流程

​ 首先要申请一个域名,例如本次的tsky31.cn,如今域名一般都在云服务提供商的后台可以进行管理,以腾讯云为例,在申请到域名后,进行对应域名的DNS解析管理,添加进项目所需要的域名,例如question.tsky31.cn,完成解析

​ 之后在服务器中进行nginx的配置即可。

10.统一接口形式

​ 在接口的返回部分,最好是可以统一形式,采用创建一个类的方式来达到目的。其中进行重载,以适应不同的返回情况

package com.sky31.domain;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
    /**
     * 状态码
     */
    private Integer code;
    /**
     * 提示信息,如果有错误时,前端可以获取该字段进行提示
     */
    private String msg;
    /**
     * 查询到的结果数据,
     */
    private T data;

    public ResponseResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResponseResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public ResponseResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

11.tomcat日志查看

​ 关于tomcat的日志,启动部分的成功与否,以及报错信息,存放在tomcat目录下/logs中的catalina.xxxx-xx-xx.log文件中,可以使用cat命令查看;而成功启动后的信息,如请求接口时终端的输出,则存放在目录中的catalina.out文件中,可以使用tail -f的命令进行查看。

12.内网穿透

​ 因此次服务器环境较为特殊,所以跟着教程学习了内网穿透,使用了工具frp。

13.拦截器配置

​ 关于SpringBoot的拦截器,可建一名为interceptor的软件包,在包下进行拦截器的编写。使拦截器类实现HandlerInterceptor这个接口,选择性重写其中的perHandlepostHandleafterCompletion方法。

​ 之后新建配置类,实现WebMvcConfigurer接口,在其中的addInterceptors方法中注册编写的拦截器类即可。其中拦截器注册类拥有增加排除路径的方法。

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Autowired
    private MessageInterceptor messageInterceptor;

    @Autowired
    private DataInterceptor dataInterceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .excludePathPatterns();
        registry.addInterceptor(messageInterceptor)
                .excludePathPatterns();
        registry.addInterceptor(dataInterceptor)
                .excludePathPatterns();
    }
}

14.UV与DAU的统计

​ 利用拦截器的特性。我们可以将uv与dau的统计功能使用redis+拦截器进行实现。因preHandle方法会在请求到达dispatcherServlet前进行拦截,所以我们可以在其中获得用户的ip地址,根据ip进行UV的统计,按照年份天数等标准构建redis中的key,进而存放到redis中。

​ 而DAU则是获取当前用户的信息,例如可以从token中提取等,判空后进行记录。

15.用户的保存

​ 因许多接口都需要获取当前用户的身份,但每次都要用HttpServletRequest中获取,十分麻烦。如果想要在service、dao层中使用,就需要从controller层层传递。所以我们可以创建一个实用类来解决。

​ 使用方式就是利用ThreadLocal类进行保存,将用户信息保存在线程中。浏览器每一次请求就是启动了一个线程,当请求结束,我们将用户的信息销毁即可

​ 实现方式:

  • 我们需要创建一个ThreadLocal类,创建一个ThreadLocal对象,设置ThreadLocal的set,remove,get方法
  • 定义一个登录的拦截器类,实现HandlerInterceptor ,重写 preHandle() 和afterCompletion()方法 ,preHandle ()方法把登录信息写入ThreadLocal,afterCompletion()方法清除登录信息
  • 我们需要设置一些配置信息,创建一个类实现 WebMvcConfigurer ,重写addInterceptors()方法,创建一个登录拦截器类的对象,给他添加到配置中,我们就实现了ThreadLocal保存用户信息
//HostHolder.java
@Component
public class HostHolder {
    private ThreadLocal<User> users=new ThreadLocal<>();


    public void setUser(User user){
        users.set(user);
    }

    public User getUser(){
        return users.get();
    }

    public void clear(){
        users.remove();
    }
}

//在拦截器中的preHandle进行用户信息的获取
@Component
public class DataInterceptor implements HandlerInterceptor {

    public DataInterceptor() {
    }

    @Autowired
    private DataService dataService;

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //统计UV
        String ip = request.getRemoteHost();
        dataService.recordUV(ip);

        //统计DAU
        User user = hostHolder.getUser();
        if (user!=null){
            dataService.recordDAU(user.getId());
        }
        return true;
    }
}

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
 
    @Override
    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
        //此方法是获取登录信息,登录方式不一样获取方法不一样,用户信息保存用的UserInfoVO,里边具体信息自己定义即可
        UserInfoVO userInfo = getUserInfo(request);
        ThreadLocalUser.set(userInfo);
        return true;
    }
 
    @Override
    public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, Exception ex) {
        ThreadLocalUser.clear();
    }
}

16.环境配置

​ 本项目使用到了Redis、ElasticSearch、Kafka等,其中不可避免地在服务器环境搭建时会遇到各种各样的报错,这里进行一个总结。

​ Redis:

  • 一定要为服务器的redis设置密码,有不怀好意之人扫到6379端口后会放置挖矿病毒或是清空redis
  • redis的配置中要关闭保护模式,且redis默认无法远程连接,要更改bind设置。可选择注释掉
  • 在启动redis时,要使用上级目录中配置好的config文件,且可以使用-d参数后台运行

​ Kafka:

  • 在运行Kafka前,要先运行zookeeper,可以将这两个设置为系统的服务。并且有先后级关系
  • Kafka运行前,一定要在配置文件中进行集群的配置

​ ElasticSearch:

17.maven

​ 本项目构建工具选择了maven,关于pom.xml不再进行赘述。

​ 在服务端进行maven打包时,可以使用mvn clean package -Dmaven.test.skip=true来跳过测试类进行打包

18.Linux命令

​ 记录一些项目部署中经常使用的命令:

  • ps -ef |grep tomcat 在运行的进程中查找tomcat 之后可利用pid进行kill
  • kill -9 pid
  • tail -f $filename

19.统一处理报错

​ 当项目规模逐渐增大时,报错的处理就成为了一个问题。我们可以使用一个类来统一处理报错

@ControllerAdvice(annotations = Controller.class)
public class MyControllerAdvice {

    private static final Logger logger= LoggerFactory.getLogger(MyControllerAdvice.class);
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public void handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {
        logger.error("服务器发生异常"+e.getMessage());
        for (StackTraceElement element:e.getStackTrace()){
            logger.error(element.toString());
        }
        String requestHeader = request.getHeader("x-requested-with");
        if (requestHeader.equals("XMLHttpRequest")){
            response.setContentType("application/plain;charset=utf-8");
            PrintWriter writer=response.getWriter();
            writer.write(Md5AndJsonUtil.getJSONString(1,"服务器异常"));
        }else{
            response.sendRedirect(request.getContextPath()+"/error");
        }
    }

}

20.git

​ 起初打算采用团队形式进行开发。但因特殊原因,后端实际情况仅我一人。但仓库是已经配好的。这次学习到了使用.gitignore文件进行非上传文件的设置,只上传必要的文件,使其他用户方便使用。

HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/

21.application.yaml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/****?characterEncoding=utf-8&userSSL=false
    username: root
    password: 
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
  kafka:
    bootstrap-servers: localhost:9092
    consumer:
      group-id: test-consumer-group
      enable-auto-commit: true
      auto-commit-interval: 3000
  task:
    execution:
      pool:
        core-size: 5
        max-size: 15
        queue-capacity: 100

    scheduling:
      pool:
        size: 5

  elasticsearch:
    uris: localhost:9200

mybatis-plus:
  global-config:
    db-config:
      id-type: none
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl


2022.9.6 补

22.chmod

​ 近期因新生返校,并且由于不是所有用户都会具备校园网内网环境的问题,我需要将项目部署运行在生产机上。虽说早有心理准备生产机的环境配置会遇到与之前不一样的问题,但还是没想到有这么多。从chmod开始一一罗列如下:

​ 首先因生产机安全考虑,我们并不能拥有root用户,而是以开放了一定权限的普通用户作为操作者。在使用sudo rz -E命令上传文件后,默认权限为644状态,这就会导致几乎所有的环境部署都无法实现了,我们使用chmod 755 -R $directoryname递归修改文件夹下的文件权限,这样才可以使得如nginx代理访问静态资源报403部分框架无法正常拉起目录内文件导致无法启动等问题不再发生

23.elasticsearch-head报错406

​ 首先分析406,开头是4,问题发生在客户端部分,着重考虑是否是客户端出了问题。406为客户端无法解析服务端发送回的数据。那么思路应该为服务端发送了什么特殊类型的数据,导致客户端无法成功解析。于是乎向着ES框架的配置文件进行搜索。

{"error":"Content-Type header [application/x-www-form-urlencoded] is not supported","status":406}报错内容如上

​ 查找资料后发现需要修改ES中的_site/vendor.js文件,修改内容如下:

  • 首先docker exec -it $container's hashcode or name /bin/bash进入容器内部

  • apt-get update更新apt,防止安装失败

  • apt-get install vim安装vim

  • vim /_site/vendor.js修改文件

  • 修改 vendor.js 共有两处,重启head插件
    vi _site/vendor.js

    6886行
    contentType: "application/x-www-form-urlencoded
    改成
    contentType: “application/json;charset=UTF-8”
    7573行
    var inspectData = s.contentType === “application/x-www-form-urlencoded” &&
    修改为
    var inspectData = s.contentType === “application/json;charset=UTF-8” &&

24.elasticsearch删除索引内数据,但不删除索引结构

​ 因为换到了新机器上,但是为了不出现大问题,就将测试机配置好的ES移动到了生产机,那么至少要解决的问题就是清空掉在测试机上存储的问题内容,以免影响实际的搜索功能

​ 因为安装了es-head,所以可以在网页端进行操作

​ 起初并未在docker拉取插件的镜像,所以想着能否使用apifox这类工具,但可惜自己的服务器很容易,但不知为何学校特殊网络环境下的服务器就连接不上了。无奈只得启用插件在web端进行解决

curl -X POST总感觉也可,但还没学习到如何发送json

​ 总之最后的解决方案如下

  • 在网页端输入自己要删除的索引,最终应该是这种形式——$indexname/_delete_by_query
{
    "query":{
        "match_all":{
        }
    }
}

25.MVC设计模式

​ 这个没啥好写的,就是重新温习了一下MVC设计模式罢了。但是挺有意义,写在这里吧

26.Nginx+docker

​ 因为这次的环境部署都是我负责,所以我又要去学习了nginx和docker这两样东西。

​ 一个意外是以往很多届成员写的nginx配置竟然存在一些问题,导致部署的时候耽误了一些时间。

​ 这次也是终于明白了Nginx为什么叫反向代理,它应该如何配置文件。docker里的image和container到底是什么关系,人们都说docker香,那么到底好在哪里?

27.注意外部包完整性

​ 有这么一个问题。因为另外有一个非常小的项目是他人开发,我负责部署,但是我用的是2.7.2的springboot,而他用的是2.7.3,导致他的项目在服务端tomcat运行时找不到2.7.3的外部包,于是便无法运行,只能访问静态资源,接口无法调用。

28.maven安装

​ 其实在maven的使用过程中,服务器也遇到很多配置问题,但是好像解决的太顺利了,现在不记得了……

​ 索性贴一个安装教程吧 CentOS7——安装配置Maven(Apache Maven)_Hern(宋兆恒)的博客-CSDN博客_centos maven安装与配置

29.Tomcat 404

​ 遇到一个很恼人的问题,Tomcat里的日志文件启动是没有报错的,因为404导致也无法分析.out文件,所以就一直在猜到底是哪里的问题

​ 网上很容易可以搜到一些常见的解决方式,我这次碰见的是自己一个疏忽忘记掉的。

​ 记得检查一下项目文件中有没有配置serverletinitializer这个类

/**
 * @AUTHOR Zzh
 * @DATE 2022/8/8
 * @TIME 23:27
 */
public class CommunityServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Sky31WelcomeApplication.class);
    }
}

三.写在最后

​ 虽然写了很多代码,加起来删删改改的也得有几千行了。如果加一些小功能,希望能冲着1W行的目标写一下。给学校部门写这个网站是没有所谓的什么钱或者分的,有时聊天也是笑着说搬砖罢了。有时对于参与本次项目的所有人(真正付出了的人,混子另当别论吧)感到可惜,虽然技术确实锻炼到了,但没有物质上的回报还是令人觉得五味杂陈。还是对组内的小伙伴们再次致谢。

​ 关于用户问题,因为这个项目是学校老师要求写的,虽然大家都知道竞品很多,但是也没办法。(甚至前几天还和同学赌用户会不会超过100

​ 写这个项目感觉很累,复盘时思考一个原因也是因为学生组织的人数稀缺,而稀缺的人中搞技术的又参差不齐,有些时候甚至除了你没人能干活,导致开发之外的部署、日常运维都要你来干。这就很不合理了,鲜有人意识到吧……

​ 对于工作的话,我是希望要么你就别开始,要么开始就立马结束。项目编写的时候经常一天从早到晚都在电脑前坐着,如果父母不带我出去转转,可能一天就没有什么其他活动了。还是要注意身体啊

​ 本次项目的一大遗憾是因工时原因,最终没有使用SpringSecurity,会在之后研究加入

​ 项目起初想过使用Go来编写,但最终因框架不熟悉而放弃。希望下一个项目能用Go

​ 本次项目一定程度上参考了牛客社区

​ 在19年来看,这样一个项目可以被拿来当作简历上的项目来宣传,但在如今,这样的项目也早已烂大街了。

​ 这样也算是写过一个完整的Web开发项目,也在实验室写过了数据处理类的项目。十分圆满,总算能让浮躁的心静下来一点,之后还是着手408的学习吧。

​ 在项目的编写过程中,遇到很多毛躁的时刻,有时因服务器环境配置需要推翻重来,有时因数据结构的复杂不知如何下手。导致有时自言自语说了很多负能量的话,感谢和我合作的同学以及父母的鼓励

​ 目前打算使用Go手写一个RPC框架,或是尝试在Windows平台下使用DLL进行多语言合作编写一个程序,不再懊恼于大一学了很多语言的语法,却没有深入过这一点。

​ 2022.9.2 深夜

posted @ 2022-09-02 23:00  Appletree24  阅读(162)  评论(0编辑  收藏  举报