仿牛客项目(持续更新)

技术架构与功能模块

仿牛客项目地址https://gitee.com/Sher-Locked/niuke

技术架构

  • SpringBoot
  • Spring、SpringMVC、MyBatis、Thymeleaf
  • MySQL、Hikari
  • Redis、Kafka、ElasticSearch
  • SpringSecurity、SpringActuator

功能模块

用户:

  • 注册登录
  • 个人主页
  • 更换头像
  • 修改密码

论坛:

  • 发布帖子
  • 评论回复
  • 敏感词过滤
  • 点赞功能
  • 搜索功能
  • 置顶、加精、删除

交友:

  • 私信聊天
  • 关注、粉丝
  • 查看他人主页

-----------------------------------------------------

项目调试技巧(Logger)

断点调试和日志功能。
日志:

//properties
logging.level.com.example.niuke = debug
logging.file.name=d:/log/niuke/xxx.log //存放日志路径

//java
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");

发送邮件(Email)

  1. 启用邮箱SMTP服务(略)
  2. 加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
  1. 配置properties
spring.mail.host=smtp.qq.com
spring.mail.port=465
spring.mail.username=xxxx@qq.com
spring.mail.password=xx.xx.xx
spring.mail.protocol=smtp
spring.mail.properties.mail.smtp.ssl.enable = true
  1. email工具类
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(message);
messageHelper.setFrom(from);
messageHelper.setTo(to);
messageHelper.setSubject(subject);
messageHelper.setText(content, true);
mailSender.send(messageHelper.getMimeMessage());
  1. 结合Thymeleaf发送html
Context content = new Context();//Thymeleaf的
content.setVariable("username", "愿你被世界温柔以待!");
String process = templateEngine.process("/email/demo", content);
mailClient.sendMail(to, subject, process);

Cookie 和 Session(会话管理)

Cookie
可以通过@CookieValue("code") String code获取对应的cookie值

//创建cookie
public void setCookie(HttpServletResponse res) {
	Cookie cookie = new Cookie("code", CommunityUtil.generateUUID());
	cookie.setPath("/test");//cookie作用范围
	cookie.setMaxAge(60*10);//cookie存活时间
	res.addCookie(cookie);//添加cookie
	return "set Cookie";
}

Session
通过session.getAttribute(" ");取值

public String test(HttpSession session){
    session.setAttribute("id", 1);
    session.setAttribute("naem","dongfang");
    return "set session";
}

分布式不用Session的原因:
多服务器时可能在服务端有不同的session,产生session共享问题
解决方案:Session一致性(我的另一篇博文)

Kaptcha生成验证码

  1. 导包
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>
  1. 配置
@Bean
public Producer kaptchaProducer() {
    Properties properties = new Properties();
    properties.setProperty("kaptcha.image.width", "100");
    properties.setProperty("kaptcha.iamge.height", "40");
    properties.setProperty("kaptcha.textproducer.font.size", "32");
    properties.setProperty("kaptcha.textproducer.font.color", "0,0,0");
    properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQ");
    properties.setProperty("kaptcha.textproducer.char.length", "4");
    properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");

    DefaultKaptcha kaptcha = new DefaultKaptcha();
    Config config = new Config(properties);
    kaptcha.setConfig(config);
    return kaptcha;
}
  1. 使用
String text = kaptchaProducer.createText();
BufferedImage image = kaptchaProducer.createImage(text);

//验证码存入session
session.setAttribute("kaptcha",text);
//给浏览器声明返回的类型
response.setContentType("image/png");
try {
    OutputStream os = response.getOutputStream();
    ImageIO.write(image, "png", os);
} catch (IOException e) {
    e.printStackTrace();
}

拦截器(HandlerInterceptor)

拦截器的应用:

  • 请求时开始查询登录用户
  • 本次请求中持有用户数据
  • 模板视图上显示用户数据
  • 请求结束时清理用户数据
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
  
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginTicketInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
    }
}

ThreadLocal使多用户登录不冲突

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

过滤敏感词

前缀树:

  • 名称:Trie、字典树、查找树
  • 特点:查找效率高,消耗内存大
  • 应用:字符串检索、词频统计、字符串排序

敏感词过滤器:

  • 定义前缀树
  • 根据敏感词,初始化前缀树
  • 编写过滤敏感词的方法

AJAX异步请求(页面不刷新)

οnclick="send();"

function send() {
	$.post(
		"/xxx/xxx",
		{"name":"xxx","age":"xxx"},
		function(data) {
			console.log(data);
		}
	)
}

防止脚本注入

discussPost.setTitle(HtmlUtils.htmlEscape(discussPost.getTitle()));//防止注入
discussPost.setContent(HtmlUtils.htmlEscape(discussPost.getContent()));//防止注入

事务管理(ACID)

ACID

  • 悲观锁:1.共享锁 2.排他锁
  • 乐观锁

Spring事务管理

  • 声明式事务
    @Transaction(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)

  • 编程式事务

@Autowired
private TransactionTemplate transactionTemplate;
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITED);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

return transactionTemplate.execute(new TransactionCallback<Object>(){ 重写方法 });

统一异常处理

@ControllerAdvice 修饰类
@ExceptionHandler 修饰方法
@ModelAttribute 修饰方法
@DataBinder 修饰方法

统一记录日志(AOP)

AOP的实现:

  • AspectJ(编译时增强)(字节码)
  • SpringAOP(运行时增强)(代理)

SpringAOP:

  • JDK动态代理
  • CGLib动态代理

集成Redis

Redis编程式事务

@Test
public void testTransactional() {
    Object obj = redisTemplate.execute(new SessionCallback() {
        @Override
        public Object execute(RedisOperations redisOperations) throws DataAccessException {
            String redisKey = "test";
            //启用事务
            redisOperations.multi();

            redisOperations.opsForSet().add(redisKey, "dongfang");
            redisOperations.opsForSet().add(redisKey, "wangquan");
            redisOperations.opsForSet().add(redisKey, "tushan");

            return redisOperations.exec();
        }
    });
}

Redis对登录模块的优化

  • 使用Redis存储验证码
    验证码需要频繁的访问与刷新,对性能要求较高
    验证码不需永久保存,通常在很短的时间后就会失效
    分布式部署时,存在Session共享的问题
  • 使用Redis存储登录凭证
    处理每次请求时,都要查询用户的登录凭证,访问的频率非常高
  • 使用Redis缓存用户信息
    处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高

Kakfa

Kafka 是一个分布式流媒体平台。

应用:消息系统、日志收集、用户行为追踪、流式处理。

特点:高吞吐量、消息持久化、高可靠性、高扩展性。

zookeeper-server-start.bat config\zookeeper.properties 开启zookeeper
kafka-server-start.bat config\server.properties 开启kafka
kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test 新建类别
kafka-console-producer.bat --broker-list localhost:9092 --topic test 开启生产者
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic test --from-beginning 开启消费者

阻塞队列

  • BlockingQueue
    解决线程通信的问题
    阻塞方法:put、take

  • 生产者消费者模式
    生成者:产生数据的线程
    消费者:使用数据的线程

  • 实现类
    ArrayBlockingQueue
    LinkedBlockingQueue
    PriorityBlockingQueue

SpringBoot 集成 Kafka

  1. 导包
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
  1. zookeeper-server-start.bat config\zookeeper.properties 开启zookeeper
    kafka-server-start.bat config\server.properties 开启kafka
  2. 配置Properties
#kafka
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=niuke-consumer-group
spring.kafka.consumer.enable-auto-commit=true
#ms
spring.kafka.consumer.auto-commit-interval=3000
  1. springboot调用
@SpringBootTest
public class KafkaTest {
    @Resource
    private KafkaProducer kafkaProducer;
    @Test
    public void testKafka() {
        kafkaProducer.sendMessage("test", "你好");
        kafkaProducer.sendMessage("test", "在吗");
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
@Component
class KafkaProducer{
    @Resource
    private KafkaTemplate kafkaTemplate;
    public void sendMessage(String topic, String content) {
        kafkaTemplate.send(topic, content);
    }
}
@Component
class KafkaConsumer {
    @KafkaListener(topics = {"test"})
    public void handleMessage(ConsumerRecord record) {
        System.out.println(record.value());
    }
}

ElasticSearch + 与SpringBoot整合

分布式RestFul 风格的搜索引擎,各种类型的数据的检索,实时搜索服务,PB级数据

术语:
索引(数据库)、类型(表)、文档(行)(Json)、字段(列)。
集群、节点、分片(shards)、副本(replicas)(备份)。

配置:

  1. 修改elasticsearch.yml
  2. 配置bin环境变量
  3. 下载ik分词器(版本对应),解压在/plugins/ik

curl -X GET "localhost:9200/_cat/health?v" 查看健康状况
curl -X GET "localhost:9200/_cat/nodes?v" 查看连接状况
curl -X GET "localhost:9200/_cat/indices?v" 查看索引
curl -X PUT "localhost:9200/test" 新建索引
curl -X DELETE "localhost:9200/test" 删除索引

  1. 导包
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
  1. 配置properties
# ElasticSearch
spring.elasticsearch.rest.username=niuke
# 9200http 9300tcp
spring.elasticsearch.rest.uris=http://localhost:9200
  1. 配置entity
@Document(indexName = "discusspost", indexStoreType = "_doc", shards = 6, replicas = 3)
public class DiscussPost {

    @Id
    private int id;
    @Field(type = FieldType.Integer)
    private int userId;
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String title;
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String content;
    @Field(type = FieldType.Integer)
    private int type;//0-普通,1-置顶
    @Field(type = FieldType.Integer)
    private int status;//0-正常,1-精华,2-拉黑
    @Field(type = FieldType.Date)
    private Date createTime;
    @Field(type = FieldType.Integer)
    private int commentCount;
    @Field(type = FieldType.Double)
    private double score;

  1. 配置dao
@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {
}
  1. 使用
NativeSearchQuery build = new NativeSearchQueryBuilder()
    .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
    .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
    .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
    .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
    .withPageable(PageRequest.of(0, 10))
    .withHighlightFields(
            new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
            new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
    ).build();

页面生成CSRF

<meta name="_csrf" th:content="${_csrf.token}">
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function (e, xhr, options){
	xhr.setRequestHeader(header, token)
});

置顶、加精、删除

  1. 导包
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
  1. 使用
<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

对字段 sec:authorize="hasAnyAuthority('moderator')

一些JS代码

$("#wonderfulButton").attr("disabled","disabled"); 设置其不可用
location.href = "/index";  跳转页面

Redis高级数据类型

HyperLogLog(合并和去重)

  • 采用一种基数算法,用于完成独立总数的统计
  • 占据空间小,无论多少数据,只占12K内存
  • 不精确,标准误差:0.81%

Bitmap(对位的运算)

  • 不是一种独立的数据结构,实际上就是字符串
  • 支持按位存取数据,可以将其看成byte数组
  • 适合存储索引大量的连续的数据的布尔值

-----------------------------------------------------

功能架构

用户:

  • 注册:MD5加密密码,邮箱激活账号
  • 登录:Session + Kaptcha + Cookie +ThreadLocal(重构后:Cookie + Redis + Cookie | ThreadLocal + Redis
  • 个人主页
  • 更换头像:图像URL与本地路径关联 JavaIO流 (重构后:云存储
  • 登录状态(防止未登录进入setting等页面)(使用:自定义注解+拦截器
  • 修改密码

论坛:

  • 发布帖子:AJAX + FastJSON实现异步
  • 评论回复
  • 敏感词过滤:Trie树
  • 点赞功能:Redis实现(set)

交友:

  • 私信聊天:MySQL存储
  • 关注、粉丝:Redis实现(zset)
  • 查看他人主页

⭐记录一些错误

  1. 当把一些用户删除的时候,他们的推文还在,导致展示推文的时候会查不到这个用户以及推文。
    报错信息:
    There was an unexpected error (type=Internal Server Error, status=500). An error happened during template parsing (template: "class path resource [templates//index.html]") org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates//index.html]")

  2. 同样:我将149号用户删除了呜呜呜呜,找不到用户的名字,一直报错

记录一些改错方法

  1. 查看报错信息,定位报错位置
  2. 根据报错信息,找出报错原因
  3. 根据报错原因, 纠正相关代码
  4. 找不到原因时:
    4.1 断点调试
    4.2 logger打印日志信息
    技巧:输出错误相关关键语句,细心查看日志

后记

@Autowired和@Resource的区别

Hikari和Druid的区别

Spring
Impl实现同一个接口时,实现IOC时对于容器难以判断 1.加@Primary注解2.@Repository时加name,注入时加@Qualifier

HTTP:request.getMethod .getServletPath .getHeadernames 对应发送HTTP的头文本
HTTP:response.setContentType(“text/html;charset=utf-8”) 对应返回HTTP的头文本

@RequestParam的一些参数 name=" " 对应前端name,required=" " ,defaultValue=" " 默认值

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Kafka是一个高性能的分布式消息队列系统,可以实现高吞吐量、低延迟的消息传递。它支持点对点和发布-订阅两种消息传递模式。在仿牛客项目中使用Kafka可以实现消息的异步处理和分布式架构。 使用Kafka的第一步是创建一个主题(topic),主题既是消息的类别,也是消息在Kafka中的存储位置。可以使用命令行工具kafka-topics.bat来创建主题。例如,可以使用以下命令来创建一个名为test的主题: bin\windows\kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test 上述命令中,--bootstrap-server参数指定了Kafka服务器的地址和端口,--replication-factor参数指定了主题的副本数,--partitions参数指定了主题的分区数。创建主题后,可以向主题中发送消息,并由消费者进行消费。 要列出已经存在的主题,可以使用以下命令: kafka-topics.bat --list --bootstrap-server localhost:9092 需要注意的是,以上命令中的localhost:9092是Kafka服务器的地址和端口,根据实际情况进行修改。 总结起来,在仿牛客项目中使用Kafka,首先需要创建一个主题,然后可以使用相关命令行工具进行消息的发送和消费。这样可以实现消息的异步处理和分布式架构。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [仿牛客论坛项目学习记录——5 Kafka 构建TB级异步消息系统](https://blog.csdn.net/dadayangpei/article/details/127173098)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值