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

一.开发进度

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

项目首页

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

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

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

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

二.项目大纲

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

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

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

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

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

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

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

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

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

  • AUTO,配合数据库设置自增主键,可以实现主键的自动增长,类型为nmber;
  • INPUT,由用户输入;
  • NONE,不设置,等同于INPUT;
  • ASSIGN_ID,只有当用户未输入时,采用雪花算法生成一个适用于分布式环境的全局唯一主键,类型可以是String和number;
  • ASSIGN_UUID,只有当用户未输入时,生成一个String类型的主键,但不保证全局唯一;

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

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

@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正常出现。

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,则表示会话结束后过期。
对于大部分浏览器而言,关闭所有浏览器窗口意味着会话结束。

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

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

@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 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);
        }

    }
}

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

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

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

​ 如果要使用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;
    #&#x8BF7;&#x6309;&#x7167;&#x4EE5;&#x4E0B;&#x534F;&#x8BAE;&#x914D;&#x7F6E;
    ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    #&#x8BF7;&#x6309;&#x7167;&#x4EE5;&#x4E0B;&#x5957;&#x4EF6;&#x914D;&#x7F6E;&#xFF0C;&#x914D;&#x7F6E;&#x52A0;&#x5BC6;&#x5957;&#x4EF6;&#xFF0C;&#x5199;&#x6CD5;&#x9075;&#x5FAA; openssl &#x6807;&#x51C6;&#x3002;
    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;
}

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

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

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

package com.sky31.domain;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult {
    /**
     * 状态码
     */
    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;
    }
}

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

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

​ 关于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();
    }
}

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

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

​ 因许多接口都需要获取当前用户的身份,但每次都要用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 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();
    }
}

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

​ Redis:

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

​ Kafka:

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

​ ElasticSearch:

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

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

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

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

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

@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");
        }
    }

}

​ 起初打算采用团队形式进行开发。但因特殊原因,后端实际情况仅我一人。但仓库是已经配好的。这次学习到了使用.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/

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 补

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

​ 首先因生产机安全考虑,我们并不能拥有root用户,而是以开放了一定权限的普通用户作为操作者。在使用 sudo rz -E命令上传文件后,默认权限为 644状态,这就会导致几乎所有的环境部署都无法实现了,我们使用 chmod 755 -R $directoryname递归修改文件夹下的文件权限,这样才可以使得如 nginx&#x4EE3;&#x7406;&#x8BBF;&#x95EE;&#x9759;&#x6001;&#x8D44;&#x6E90;&#x62A5;403&#x90E8;&#x5206;&#x6846;&#x67B6;&#x65E0;&#x6CD5;&#x6B63;&#x5E38;&#x62C9;&#x8D77;&#x76EE;&#x5F55;&#x5185;&#x6587;&#x4EF6;&#x5BFC;&#x81F4;&#x65E0;&#x6CD5;&#x542F;&#x52A8;等问题不再发生

​ 首先分析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” &&

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

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

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

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

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

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

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

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

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

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

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

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

​ 遇到一个很恼人的问题,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 深夜

Original: https://www.cnblogs.com/appletree24/p/16651618.html
Author: Appletree24
Title: 湘潭大学新生匿名问答网站——解湘 项目总结

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/584822/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • [转]Spring Security打造一个简单Login登录页面,实现登录+跳转+注销+角色权限功能,核心代码不到100行!

    原文链接:Spring Security打造一个简单Login登录页面,实现登录+跳转+注销+角色权限功能,核心代码不到100行! posted @2022-07-08 18:31…

    Java 2023年5月29日
    095
  • JavaWeb_(视频网站)_七、推荐模块1

    package com.Gary.betobe.recommend; import java.util.Set; import org.springframework.beans….

    Java 2023年5月29日
    070
  • 微服务SpringCloud之Spring Cloud Config配置中心SVN

    在回来的路上看到一个个的都抱着花,吃了一路的狗粮,原本想着去旁边的工业园里跑跑步呢,想想还是算了,人家过七夕,俺们过巴西。上一博客学习了Spring Cloud Config使用g…

    Java 2023年5月30日
    065
  • 【leetcode】 15. 三数之和

    题目 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。 注意…

    Java 2023年6月6日
    0101
  • 简单说说Runnable和Callable

    Runnable和Callable这两个接口,是并发编程不可避免要谈的话题,而且总要被放到一起比较一番。太多的人写这两者之间的对比和差异了,在这里就只是随手记录一下自己的理解和想法…

    Java 2023年6月5日
    060
  • 68.可能否

    sfsd posted @2022-09-28 08:39 随遇而安== 阅读(5 ) 评论() 编辑 Original: https://www.cnblogs.com/55zj…

    Java 2023年6月7日
    063
  • git 常用操作

    可以把分支名理解为指针,比如 master,test,origin/master 等,都是一个指针,指向某次提交快照特殊指针HEAD:Git有一个名为 HEAD 的特殊指针,它是一…

    Java 2023年6月9日
    095
  • Java8的foreach循环如何获取对象的index下标

    在Java8中,我们经常使用lambada表达式进行foreach循环,但是常常我们在遍历List的时候想获取对象的index,但是Java8、9、10、11都没有相关的支持,同样…

    Java 2023年6月7日
    081
  • Java线程的6种状态转换

    Java线程的生命周期 与操作系统中线程的五种状态区分开,Java线程有以下6种状态: New 新建 Runnable 可运行 Blocked 阻塞 Waiting 等待 Time…

    Java 2023年6月5日
    0136
  • Spring系列14:IoC容器的扩展点

    Spring系列14:IoC容器的扩展点 回顾 知识需要成体系地学习,本系列文章前后有关联,建议按照顺序阅读。上一篇我们详细介绍了Spring Bean的生命周期和丰富的扩展点,没…

    Java 2023年6月5日
    097
  • HTTP协议学习之Request学习

    在开始前,我们首先对HTTP协议做个简单的了解 HTTP协议(Hyper Text Transfer Protocol) 超文本传输协议 名词非常的高大上,如果学过计算机网络这门课…

    Java 2023年6月5日
    085
  • Linux 搞乱、有趣的命令

    1、sl命令 你会看到一辆火车从屏幕右边开往左边…… 安装 sudo yum -y install sl 运行 sl 命令有 -a l F e几个选项,加上…

    Java 2023年6月5日
    067
  • Sublime Text 编译 运行 Java 源代码 包 类文件

    Sublime Text 编译 Java 包 更新记录 2022/05/23 解决SublimeText控制台用户输入问题 前言 目前还存在很多问题,不过暂时能用,就先不折腾了,等…

    Java 2023年6月5日
    067
  • springboot application.properties不生效

    开始一直提示 Failed to configure a DataSource: &#x2018;url&#x2019; attribute is not spec…

    Java 2023年6月8日
    086
  • 非易失性规划的原理与实现方法

    常言道,计划不如变化快。计划的制定本身就建基于对未来一定时间范围内的环境条件假设,当计划制定后到执行完成的时间段内,若环境条件发生变化,那么计划也需要进行适当的调整才能满足实际要求…

    Java 2023年6月16日
    076
  • JSP基础知识总结

    JSP概述 什么是 jsp Servlet 程序输出 html 页面 如何创建一个 jsp 动态页面程序 如何修改 jsp 文件的默认编码 jsp 的运行原理 jsp 的语法 js…

    Java 2023年6月15日
    071
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球