作为一个动漫爱好者,我一直想开发一个专门针对科幻和宇宙题材动漫的垂直社区平台。经过三个月的开发,终于完成了这个基于Spring Boot框架的宇宙动漫网站。这个项目不仅满足了我的个人兴趣,也让我对Java Web开发有了更深入的理解。
这个宇宙动漫网站采用了典型的B/S架构,前端使用Vue.js构建用户界面,后端基于Spring Boot框架开发,数据库选用MySQL 8.0。系统主要分为用户端和管理端两大模块,实现了动漫信息展示、用户订阅、社区互动、内容管理等核心功能。
Spring Boot是我选择的后端框架,主要基于以下几个考虑:
快速开发:Spring Boot的自动配置和起步依赖大大减少了配置工作,让我能快速搭建项目骨架。比如只需在pom.xml中添加spring-boot-starter-web依赖,就能立即拥有一个可运行的Web应用。
内嵌服务器:内置Tomcat服务器,无需额外部署,开发调试非常方便。通过简单的mvn spring-boot:run命令就能启动应用。
丰富的生态系统:Spring生态中有大量现成的解决方案,如Spring Security用于认证授权,Spring Data JPA简化数据库操作等。
微服务友好:如果未来需要扩展为微服务架构,Spring Cloud能无缝集成。
前端部分我选择了Vue.js + Element UI的组合:
javascript复制// 典型的前端组件结构
<template>
<div class="anime-card">
<el-image :src="anime.cover" fit="cover"></el-image>
<div class="anime-info">
<h3>{{ anime.title }}</h3>
<p>{{ anime.description }}</p>
<el-button @click="subscribe">订阅</el-button>
</div>
</div>
</template>
<script>
export default {
props: ['anime'],
methods: {
subscribe() {
this.$axios.post('/api/subscribe', { animeId: this.anime.id })
.then(response => {
this.$message.success('订阅成功!');
});
}
}
}
</script>
Vue的响应式特性和组件化开发模式,使得前端代码更易于维护和扩展。Element UI提供了丰富的UI组件,大大加快了开发进度。
数据库选用MySQL 8.0,主要表结构设计如下:
sql复制CREATE TABLE `animes` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL COMMENT '动漫名称',
`category` varchar(50) COMMENT '动漫分类',
`cover_url` text COMMENT '封面URL',
`region` varchar(50) COMMENT '所属地区',
`author` varchar(100) COMMENT '动漫作者',
`publish_year` varchar(10) COMMENT '发行年份',
`description` text COMMENT '剧情简介',
`video_url` text COMMENT '视频链接',
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
考虑到动漫信息中封面和视频都是大文件,实际存储时只保存URL,文件本身存放在阿里云OSS等对象存储服务中。
系统采用JWT(JSON Web Token)实现无状态认证,流程如下:
Spring Security配置示例:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
管理员可以通过后台添加和管理动漫信息,核心代码如下:
java复制@RestController
@RequestMapping("/api/admin/animes")
public class AnimeAdminController {
@Autowired
private AnimeService animeService;
@PostMapping
public ResponseEntity<?> createAnime(@Valid @RequestBody AnimeDTO animeDTO) {
Anime anime = animeService.createAnime(animeDTO);
return ResponseEntity.ok(anime);
}
@GetMapping("/{id}")
public ResponseEntity<?> getAnime(@PathVariable Long id) {
Anime anime = animeService.getAnimeById(id);
return ResponseEntity.ok(anime);
}
@PutMapping("/{id}")
public ResponseEntity<?> updateAnime(@PathVariable Long id,
@Valid @RequestBody AnimeDTO animeDTO) {
Anime anime = animeService.updateAnime(id, animeDTO);
return ResponseEntity.ok(anime);
}
}
用户订阅动漫的核心逻辑:
java复制@Service
public class SubscriptionServiceImpl implements SubscriptionService {
@Autowired
private SubscriptionRepository subscriptionRepository;
@Autowired
private AnimeRepository animeRepository;
@Autowired
private UserRepository userRepository;
@Override
@Transactional
public Subscription subscribeAnime(Long userId, Long animeId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
Anime anime = animeRepository.findById(animeId)
.orElseThrow(() -> new ResourceNotFoundException("Anime not found"));
// 检查是否已订阅
if (subscriptionRepository.existsByUserAndAnime(user, anime)) {
throw new BusinessException("您已经订阅过该动漫");
}
Subscription subscription = new Subscription();
subscription.setUser(user);
subscription.setAnime(anime);
subscription.setSubscribeDate(LocalDate.now());
return subscriptionRepository.save(subscription);
}
}
为提高系统响应速度,我对以下数据实施了缓存:
Spring Cache配置示例:
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
@Service
public class AnimeServiceImpl implements AnimeService {
@Cacheable(value = "anime", key = "#id")
public Anime getAnimeById(Long id) {
// 数据库查询逻辑
}
@CacheEvict(value = "anime", key = "#id")
public void updateAnime(Long id, AnimeDTO animeDTO) {
// 更新逻辑
}
}
索引优化:为常用查询字段添加索引
sql复制ALTER TABLE `animes` ADD INDEX `idx_category` (`category`);
ALTER TABLE `subscriptions` ADD INDEX `idx_user_anime` (`user_id`, `anime_id`);
查询优化:使用JOIN减少查询次数
java复制@Query("SELECT a FROM Anime a LEFT JOIN FETCH a.comments WHERE a.id = :id")
Anime findAnimeWithComments(@Param("id") Long id);
分页处理:所有列表接口都实现分页
java复制@GetMapping
public Page<Anime> getAnimes(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return animeRepository.findAll(PageRequest.of(page, size));
}
用户密码使用BCrypt加密存储:
java复制@Service
public class UserServiceImpl implements UserService {
@Override
public User register(UserDTO userDTO) {
User user = new User();
user.setUsername(userDTO.getUsername());
user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
// 其他字段设置
return userRepository.save(user);
}
}
敏感操作记录日志:
java复制@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@AfterReturning(pointcut = "execution(* com..admin..*(..))", returning = "result")
public void logAdminOperation(JoinPoint joinPoint, Object result) {
logger.info("Admin operation: {} - Result: {}",
joinPoint.getSignature().getName(), result);
}
}
使用Jenkins实现自动化部署流程:
对应的Jenkinsfile示例:
groovy复制pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
}
stage('Deploy') {
steps {
sh 'docker build -t cosmic-anime .'
sh 'docker-compose up -d'
}
}
}
}
N+1查询问题:
@EntityGraph或MyBatis的关联查询一次性获取所需数据并发订阅冲突:
java复制@Table(uniqueConstraints = {
@UniqueConstraint(columnNames = {"user_id", "anime_id"})
})
public class Subscription {
// ...
}
大文件上传超时:
saveAll或MyBatis的批量插入代替循环单条插入java复制@Async
public void sendSubscribeNotification(Subscription subscription) {
// 发送通知逻辑
}
这个宇宙动漫网站目前已经实现了基本功能,但还有不少可以扩展的方向:
实现推荐系统的简单示例:
java复制@Service
public class RecommendationServiceImpl implements RecommendationService {
@Autowired
private SubscriptionRepository subscriptionRepository;
@Autowired
private AnimeRepository animeRepository;
@Override
public List<Anime> recommendAnimes(Long userId) {
// 获取用户订阅的动漫类别
List<String> favoriteCategories = subscriptionRepository
.findCategoriesByUserId(userId);
// 获取同类别中热门的动漫
return animeRepository.findPopularByCategories(
favoriteCategories, PageRequest.of(0, 10));
}
}
开发这个宇宙动漫网站的过程中,我深刻体会到系统设计的重要性。前期良好的数据库设计和模块划分,能大大减少后期的开发维护成本。同时,性能优化是一个持续的过程,需要根据实际运行情况不断调整。这个项目让我对全栈开发有了更全面的认识,也积累了宝贵的实战经验。