diff --git a/pom.xml b/pom.xml index 1e1984e..51f74d8 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,26 @@ spring-boot-starter-test test + + + + + org.xerial + sqlite-jdbc + 3.50.3.0 + + + + io.swagger.parser.v3 + swagger-parser-v3 + 2.1.34 + + + + io.swagger.parser.v3 + swagger-parser-core + 2.1.34 + @@ -48,6 +68,78 @@ org.springframework.boot spring-boot-maven-plugin + + + org.openapitools + openapi-generator-maven-plugin + 7.15.0 + + + bookmark-api + + generate + + + ${project.basedir}/src/main/resources/config/bookmark/bookmark.yaml + spring + + src/gen/java/main + false + true + true + true + true + true + true + false + + + + + user-np-api + + generate + + + ${project.basedir}/src/main/resources/config/user_np/user_np.yaml + spring + + src/gen/java/main + false + true + true + true + true + true + true + false + + + + + + diff --git a/src/main/java/com/zzyxyz/api/bookmarks/BookmarksApplication.java b/src/main/java/com/zzyxyz/api/bookmarks/BookmarksApplication.java index 2272ef0..842291e 100644 --- a/src/main/java/com/zzyxyz/api/bookmarks/BookmarksApplication.java +++ b/src/main/java/com/zzyxyz/api/bookmarks/BookmarksApplication.java @@ -2,12 +2,23 @@ package com.zzyxyz.api.bookmarks; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; @SpringBootApplication +@RestController +@ComponentScan(basePackages = {"com.zzyxyz.api.bookmarks", "org.openapitools"}) public class BookmarksApplication { public static void main(String[] args) { SpringApplication.run(BookmarksApplication.class, args); } + @GetMapping("/hello") + public String hello(@RequestParam(value = "name", defaultValue = "World") String name) { + return String.format("Hello %s!", name); + } + } diff --git a/src/main/java/com/zzyxyz/api/bookmarks/CorsConfig.java b/src/main/java/com/zzyxyz/api/bookmarks/CorsConfig.java new file mode 100644 index 0000000..43e226f --- /dev/null +++ b/src/main/java/com/zzyxyz/api/bookmarks/CorsConfig.java @@ -0,0 +1,43 @@ +package com.zzyxyz.api.bookmarks; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import jakarta.validation.constraints.NotNull; + +@Configuration +public class CorsConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(@NotNull CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") // 使用allowedOriginPatterns而不是allowedOrigins + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(false); // 设置为false避免冲突 + } + }; + } + + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.addAllowedOriginPattern("*"); // 使用addAllowedOriginPattern而不是addAllowedOrigin + config.setAllowCredentials(false); // 设置为false避免冲突 + config.addAllowedMethod("*"); + config.addAllowedHeader("*"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/src/main/java/com/zzyxyz/api/bookmarks/bookmark/Bookmark.java b/src/main/java/com/zzyxyz/api/bookmarks/bookmark/Bookmark.java new file mode 100644 index 0000000..670f915 --- /dev/null +++ b/src/main/java/com/zzyxyz/api/bookmarks/bookmark/Bookmark.java @@ -0,0 +1,92 @@ +package com.zzyxyz.api.bookmarks.bookmark; + +import java.time.OffsetDateTime; + +public class Bookmark { + private Long id; + private Long externalId; + private String name; + private String link; + private String detail; + private String description; + private OffsetDateTime createdAt; + private OffsetDateTime updatedAt; + + // Constructors + public Bookmark() {} + + public Bookmark(Long externalId, String name, String link, String detail, String description) { + this.externalId = externalId; + this.name = name; + this.link = link; + this.detail = detail; + this.description = description; + this.createdAt = OffsetDateTime.now(); + this.updatedAt = OffsetDateTime.now(); + } + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getExternalId() { + return externalId; + } + + public void setExternalId(Long externalId) { + this.externalId = externalId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(OffsetDateTime createdAt) { + this.createdAt = createdAt; + } + + public OffsetDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(OffsetDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} \ No newline at end of file diff --git a/src/main/java/com/zzyxyz/api/bookmarks/bookmark/BookmarkController.java b/src/main/java/com/zzyxyz/api/bookmarks/bookmark/BookmarkController.java new file mode 100644 index 0000000..0897064 --- /dev/null +++ b/src/main/java/com/zzyxyz/api/bookmarks/bookmark/BookmarkController.java @@ -0,0 +1,124 @@ +package com.zzyxyz.api.bookmarks.bookmark; + +import java.sql.SQLException; +import java.time.OffsetDateTime; +import org.openapitools.api.DataApi; +import org.openapitools.model.BookmarkRequest; +import org.openapitools.model.BookmarkResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CrossOrigin; + +@Controller +@CrossOrigin(origins = "*") +public class BookmarkController implements DataApi { + + @Autowired + private BookmarkDAO bookmarkDAO; + + // 初始化数据库表 + public BookmarkController() { + try { + bookmarkDAO = new BookmarkDAO(); + bookmarkDAO.createTableIfNotExists(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public ResponseEntity createBookmark(Long id, BookmarkRequest bookmarkRequest) { + try { + // 检查是否已存在相同externalId的书签 + Bookmark existingBookmark = bookmarkDAO.findByExternalId(id); + if (existingBookmark != null) { + return ResponseEntity.status(409).body(null); + } + + Bookmark bookmark = new Bookmark( + id, + bookmarkRequest.getName(), + bookmarkRequest.getLink(), + bookmarkRequest.getDetail(), + bookmarkRequest.getDescription() + ); + + Bookmark savedBookmark = bookmarkDAO.insert(bookmark); + BookmarkResponse response = convertToResponse(savedBookmark); + return ResponseEntity.status(201).body(response); + } catch (SQLException e) { + e.printStackTrace(); + return ResponseEntity.status(500).build(); + } + } + + @Override + public ResponseEntity deleteBookmark(Long id) { + try { + Bookmark bookmark = bookmarkDAO.findById(id); + if (bookmark == null) { + return ResponseEntity.notFound().build(); + } + + bookmarkDAO.delete(id); + return ResponseEntity.noContent().build(); + } catch (SQLException e) { + e.printStackTrace(); + return ResponseEntity.status(500).build(); + } + } + + @Override + public ResponseEntity getBookmark(Long id) { + try { + Bookmark bookmark = bookmarkDAO.findById(id); + if (bookmark == null) { + return ResponseEntity.notFound().build(); + } + BookmarkResponse response = convertToResponse(bookmark); + return ResponseEntity.ok(response); + } catch (SQLException e) { + e.printStackTrace(); + return ResponseEntity.status(500).build(); + } + } + + @Override + public ResponseEntity updateBookmark(Long id, BookmarkRequest bookmarkRequest) { + try { + Bookmark existingBookmark = bookmarkDAO.findById(id); + if (existingBookmark == null) { + return ResponseEntity.notFound().build(); + } + + existingBookmark.setName(bookmarkRequest.getName()); + existingBookmark.setLink(bookmarkRequest.getLink()); + existingBookmark.setDetail(bookmarkRequest.getDetail()); + existingBookmark.setDescription(bookmarkRequest.getDescription()); + existingBookmark.setUpdatedAt(OffsetDateTime.now()); + + boolean updated = bookmarkDAO.update(existingBookmark); + if (updated) { + BookmarkResponse response = convertToResponse(existingBookmark); + return ResponseEntity.ok(response); + } else { + return ResponseEntity.status(500).build(); + } + } catch (SQLException e) { + e.printStackTrace(); + return ResponseEntity.status(500).build(); + } + } + + private BookmarkResponse convertToResponse(Bookmark bookmark) { + return new BookmarkResponse() + .id(bookmark.getId()) + .name(bookmark.getName()) + .link(bookmark.getLink()) + .detail(bookmark.getDetail()) + .description(bookmark.getDescription()) + .createdAt(bookmark.getCreatedAt()) + .updatedAt(bookmark.getUpdatedAt()); + } +} diff --git a/src/main/java/com/zzyxyz/api/bookmarks/bookmark/BookmarkDAO.java b/src/main/java/com/zzyxyz/api/bookmarks/bookmark/BookmarkDAO.java new file mode 100644 index 0000000..36d0d99 --- /dev/null +++ b/src/main/java/com/zzyxyz/api/bookmarks/bookmark/BookmarkDAO.java @@ -0,0 +1,170 @@ +package com.zzyxyz.api.bookmarks.bookmark; + +import org.springframework.stereotype.Component; + +import java.sql.*; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; + +@Component +public class BookmarkDAO { + private static final String DB_URL = "jdbc:sqlite:bookmarks.db"; + + static { + try { + Class.forName("org.sqlite.JDBC"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + public void createTableIfNotExists() throws SQLException { + String sql = """ + CREATE TABLE IF NOT EXISTS bookmarks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + external_id INTEGER UNIQUE NOT NULL, + name TEXT NOT NULL, + link TEXT, + detail TEXT, + description TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + """; + + try (Connection connection = DriverManager.getConnection(DB_URL); + Statement statement = connection.createStatement()) { + statement.executeUpdate(sql); + } + } + + public Bookmark insert(Bookmark bookmark) throws SQLException { + String sql = """ + INSERT INTO bookmarks ( + external_id, name, link, detail, description, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?) + """; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + + statement.setLong(1, bookmark.getExternalId()); + statement.setString(2, bookmark.getName()); + statement.setString(3, bookmark.getLink()); + statement.setString(4, bookmark.getDetail()); + statement.setString(5, bookmark.getDescription()); + statement.setString(6, bookmark.getCreatedAt().toString()); + statement.setString(7, bookmark.getUpdatedAt().toString()); + + int affectedRows = statement.executeUpdate(); + + if (affectedRows == 0) { + throw new SQLException("Creating bookmark failed, no rows affected."); + } + + try (ResultSet generatedKeys = statement.getGeneratedKeys()) { + if (generatedKeys.next()) { + bookmark.setId(generatedKeys.getLong(1)); + } else { + throw new SQLException("Creating bookmark failed, no ID obtained."); + } + } + } + return bookmark; + } + + public Bookmark findById(Long id) throws SQLException { + String sql = "SELECT * FROM bookmarks WHERE id = ?"; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql)) { + + statement.setLong(1, id); + ResultSet rs = statement.executeQuery(); + + if (rs.next()) { + return mapResultSetToBookmark(rs); + } + } + return null; + } + + public Bookmark findByExternalId(Long externalId) throws SQLException { + String sql = "SELECT * FROM bookmarks WHERE external_id = ?"; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql)) { + + statement.setLong(1, externalId); + ResultSet rs = statement.executeQuery(); + + if (rs.next()) { + return mapResultSetToBookmark(rs); + } + } + return null; + } + + public List findAll() throws SQLException { + String sql = "SELECT * FROM bookmarks"; + List bookmarks = new ArrayList<>(); + + try (Connection connection = DriverManager.getConnection(DB_URL); + Statement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery(sql)) { + + while (rs.next()) { + bookmarks.add(mapResultSetToBookmark(rs)); + } + } + return bookmarks; + } + + public boolean update(Bookmark bookmark) throws SQLException { + String sql = """ + UPDATE bookmarks SET + name = ?, link = ?, detail = ?, description = ?, updated_at = ? + WHERE id = ? + """; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql)) { + + statement.setString(1, bookmark.getName()); + statement.setString(2, bookmark.getLink()); + statement.setString(3, bookmark.getDetail()); + statement.setString(4, bookmark.getDescription()); + statement.setString(5, OffsetDateTime.now().toString()); + statement.setLong(6, bookmark.getId()); + + int affectedRows = statement.executeUpdate(); + return affectedRows > 0; + } + } + + public boolean delete(Long id) throws SQLException { + String sql = "DELETE FROM bookmarks WHERE id = ?"; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql)) { + + statement.setLong(1, id); + int affectedRows = statement.executeUpdate(); + return affectedRows > 0; + } + } + + private Bookmark mapResultSetToBookmark(ResultSet rs) throws SQLException { + Bookmark bookmark = new Bookmark(); + bookmark.setId(rs.getLong("id")); + bookmark.setExternalId(rs.getLong("external_id")); + bookmark.setName(rs.getString("name")); + bookmark.setLink(rs.getString("link")); + bookmark.setDetail(rs.getString("detail")); + bookmark.setDescription(rs.getString("description")); + bookmark.setCreatedAt(OffsetDateTime.parse(rs.getString("created_at"))); + bookmark.setUpdatedAt(OffsetDateTime.parse(rs.getString("updated_at"))); + return bookmark; + } +} diff --git a/src/main/java/com/zzyxyz/api/bookmarks/user_np/UserNP.java b/src/main/java/com/zzyxyz/api/bookmarks/user_np/UserNP.java new file mode 100644 index 0000000..ed473f9 --- /dev/null +++ b/src/main/java/com/zzyxyz/api/bookmarks/user_np/UserNP.java @@ -0,0 +1,96 @@ +package com.zzyxyz.api.bookmarks.user_np; + +import java.time.OffsetDateTime; + +public class UserNP { + private Long id; + private String username; + private String password; + private String email; + private String token; + private OffsetDateTime createdAt; + private OffsetDateTime updatedAt; + + // Constructors + public UserNP() { + this.createdAt = OffsetDateTime.now(); + this.updatedAt = OffsetDateTime.now(); + } + + public UserNP(String username, String password, String email) { + this.username = username; + this.password = password; + this.email = email; + this.createdAt = OffsetDateTime.now(); + this.updatedAt = OffsetDateTime.now(); + } + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(OffsetDateTime createdAt) { + this.createdAt = createdAt; + } + + public OffsetDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(OffsetDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + // Hash password method (placeholder) + public void hashPassword(String rawPassword) { + // In a real implementation, you would use a proper password hashing library like BCrypt + this.password = rawPassword; // This is just a placeholder + } + + // Check password method (placeholder) + public boolean checkPassword(String rawPassword) { + // In a real implementation, you would use a proper password hashing library like BCrypt + return this.password.equals(rawPassword); // This is just a placeholder + } +} diff --git a/src/main/java/com/zzyxyz/api/bookmarks/user_np/UserNPController.java b/src/main/java/com/zzyxyz/api/bookmarks/user_np/UserNPController.java new file mode 100644 index 0000000..57343ba --- /dev/null +++ b/src/main/java/com/zzyxyz/api/bookmarks/user_np/UserNPController.java @@ -0,0 +1,142 @@ +package com.zzyxyz.api.bookmarks.user_np; + +import org.openapitools.api.AuthApi; +import org.openapitools.api.DefaultApi; +import org.openapitools.model.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CrossOrigin; + +import java.sql.SQLException; +import java.time.OffsetDateTime; +import java.util.UUID; + +@Controller +@CrossOrigin(origins = "*") +public class UserNPController implements DefaultApi, AuthApi { + + @Autowired + private UserNPDAO userDAO; + + // 初始化数据库表 + public UserNPController() { + try { + userDAO = new UserNPDAO(); + userDAO.createTableIfNotExists(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public ResponseEntity deleteUser(String username) { + try { + UserNP user = userDAO.findByUsername(username); + if (user == null) { + // 如果用户不存在,按照API规范返回200状态 + return ResponseEntity.ok().build(); + } + + userDAO.delete(user.getId()); + return ResponseEntity.ok().build(); + } catch (SQLException e) { + e.printStackTrace(); + return ResponseEntity.status(500).build(); + } + } + + @Override + public ResponseEntity updatePassword(String username, ChangePasswordRequest changePasswordRequest) { + try { + UserNP user = userDAO.findByUsername(username); + if (user == null) { + return ResponseEntity.status(401).build(); // 认证失败 + } + + // 验证旧密码 + if (!user.checkPassword(changePasswordRequest.getOldPassword())) { + return ResponseEntity.status(401).build(); // 认证失败 + } + + // 更新密码 + user.hashPassword(changePasswordRequest.getNewPassword()); + user.setUpdatedAt(OffsetDateTime.now()); + userDAO.update(user); + + return ResponseEntity.ok().build(); + } catch (SQLException e) { + e.printStackTrace(); + return ResponseEntity.status(500).build(); + } + } + + @Override + public ResponseEntity userLogin(String username, LoginRequest loginRequest) { + try { + UserNP user = userDAO.findByUsername(username); + if (user == null) { + return ResponseEntity.status(401).build(); // 认证失败 + } + + // 验证密码 + if (!user.checkPassword(loginRequest.getPassword())) { + return ResponseEntity.status(401).build(); // 认证失败 + } + + // 生成token + String token = UUID.randomUUID().toString(); + user.setToken(token); + user.setUpdatedAt(OffsetDateTime.now()); + userDAO.update(user); + + LoginResponse response = new LoginResponse(); + response.setToken(token); + response.setUserId(user.getId()); + + return ResponseEntity.ok(response); + } catch (SQLException e) { + e.printStackTrace(); + return ResponseEntity.status(500).build(); + } + } + + @Override + public ResponseEntity userRegister(String username, RegisterRequest registerRequest) { + try { + // 检查用户名是否已存在 + UserNP existingUser = userDAO.findByUsername(username); + if (existingUser != null) { + return ResponseEntity.status(409).build(); // 用户名已存在 + } + + // 创建新用户 + UserNP user = new UserNP(username, "", registerRequest.getEmail()); + user.hashPassword(registerRequest.getPassword()); + + // 保存到数据库 + UserNP savedUser = userDAO.insert(user); + + if (savedUser.getId() != null) { + return ResponseEntity.status(201).build(); + } else { + return ResponseEntity.status(500).build(); + } + } catch (SQLException e) { + e.printStackTrace(); + return ResponseEntity.status(500).build(); + } + } + + @Override + public ResponseEntity getUserInfo(String username) { + // 未实现 + return ResponseEntity.status(500).build(); + } + + @Override + public ResponseEntity saveUserInfo(String username, Object body) { + // 未实现 + return ResponseEntity.status(500).build(); + } +} diff --git a/src/main/java/com/zzyxyz/api/bookmarks/user_np/UserNPDAO.java b/src/main/java/com/zzyxyz/api/bookmarks/user_np/UserNPDAO.java new file mode 100644 index 0000000..c9938f2 --- /dev/null +++ b/src/main/java/com/zzyxyz/api/bookmarks/user_np/UserNPDAO.java @@ -0,0 +1,166 @@ +package com.zzyxyz.api.bookmarks.user_np; + +import org.springframework.stereotype.Component; +import java.sql.*; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; + +@Component +public class UserNPDAO { + private static final String DB_URL = "jdbc:sqlite:users.db"; + + static { + try { + Class.forName("org.sqlite.JDBC"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + public void createTableIfNotExists() throws SQLException { + String sql = """ + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + email TEXT, + token TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + """; + + try (Connection connection = DriverManager.getConnection(DB_URL); + Statement statement = connection.createStatement()) { + statement.executeUpdate(sql); + } + } + + public UserNP insert(UserNP user) throws SQLException { + String sql = """ + INSERT INTO users ( + username, password, email, token, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?) + """; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + + statement.setString(1, user.getUsername()); + statement.setString(2, user.getPassword()); + statement.setString(3, user.getEmail()); + statement.setString(4, user.getToken()); + statement.setString(5, user.getCreatedAt().toString()); + statement.setString(6, user.getUpdatedAt().toString()); + + int affectedRows = statement.executeUpdate(); + + if (affectedRows == 0) { + throw new SQLException("Creating user failed, no rows affected."); + } + + try (ResultSet generatedKeys = statement.getGeneratedKeys()) { + if (generatedKeys.next()) { + user.setId(generatedKeys.getLong(1)); + } else { + throw new SQLException("Creating user failed, no ID obtained."); + } + } + } + return user; + } + + public UserNP findById(Long id) throws SQLException { + String sql = "SELECT * FROM users WHERE id = ?"; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql)) { + + statement.setLong(1, id); + ResultSet rs = statement.executeQuery(); + + if (rs.next()) { + return mapResultSetToUser(rs); + } + } + return null; + } + + public UserNP findByUsername(String username) throws SQLException { + String sql = "SELECT * FROM users WHERE username = ?"; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql)) { + + statement.setString(1, username); + ResultSet rs = statement.executeQuery(); + + if (rs.next()) { + return mapResultSetToUser(rs); + } + } + return null; + } + + public List findAll() throws SQLException { + String sql = "SELECT * FROM users"; + List users = new ArrayList<>(); + + try (Connection connection = DriverManager.getConnection(DB_URL); + Statement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery(sql)) { + + while (rs.next()) { + users.add(mapResultSetToUser(rs)); + } + } + return users; + } + + public boolean update(UserNP user) throws SQLException { + String sql = """ + UPDATE users SET + username = ?, password = ?, email = ?, token = ?, updated_at = ? + WHERE id = ? + """; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql)) { + + statement.setString(1, user.getUsername()); + statement.setString(2, user.getPassword()); + statement.setString(3, user.getEmail()); + statement.setString(4, user.getToken()); + statement.setString(5, OffsetDateTime.now().toString()); + statement.setLong(6, user.getId()); + + int affectedRows = statement.executeUpdate(); + return affectedRows > 0; + } + } + + public boolean delete(Long id) throws SQLException { + String sql = "DELETE FROM users WHERE id = ?"; + + try (Connection connection = DriverManager.getConnection(DB_URL); + PreparedStatement statement = connection.prepareStatement(sql)) { + + statement.setLong(1, id); + int affectedRows = statement.executeUpdate(); + return affectedRows > 0; + } + } + + private UserNP mapResultSetToUser(ResultSet rs) throws SQLException { + UserNP user = new UserNP(); + user.setId(rs.getLong("id")); + user.setUsername(rs.getString("username")); + user.setPassword(rs.getString("password")); + user.setEmail(rs.getString("email")); + user.setToken(rs.getString("token")); + user.setCreatedAt(OffsetDateTime.parse(rs.getString("created_at"))); + user.setUpdatedAt(OffsetDateTime.parse(rs.getString("updated_at"))); + return user; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ff8aec0..94a00c4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,3 @@ spring.application.name=bookmarks +server.port=8081 +server.servlet.context-path=/api diff --git a/src/main/resources/config/bookmark/bookmark.yaml b/src/main/resources/config/bookmark/bookmark.yaml new file mode 100644 index 0000000..f9ffb0a --- /dev/null +++ b/src/main/resources/config/bookmark/bookmark.yaml @@ -0,0 +1,238 @@ +openapi: '3.0.3' +info: + title: zzyxyz_bookmark_api + description: bookmark API服务 + version: '1.0' +servers: + - url: http://localhost:8081/api + description: 开发环境 + - url: https://api.zzyxyz.com/api + description: 生产环境 +tags: + - name: data + description: 书签相关操作 +security: + - ApiKeyAuth: [] + +paths: + /bookmarks/v1/data/{id}: + parameters: + - name: id + in: path + example: 1 + required: true + schema: + type: integer + format: int64 + + post: + summary: 创建书签 + description: 在文件夹下创建一个书签 + operationId: createBookmark + tags: [data] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BookmarkRequest' + responses: + '201': + description: 创建成功的书签 + content: + application/json: + schema: + $ref: '#/components/schemas/BookmarkResponse' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '500': + $ref: '#/components/responses/ServerInternalError' + + get: + summary: 获取书签详情 + description: 通过id获取书签内容 + operationId: getBookmark + tags: [data] + responses: + '200': + description: 书签详情 + content: + application/json: + schema: + $ref: '#/components/schemas/BookmarkResponse' + '404': + description: 书签不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '500': + $ref: '#/components/responses/ServerInternalError' + + put: + summary: 更新书签 + description: 更新指定id的书签 + operationId: updateBookmark + tags: [data] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BookmarkRequest' + responses: + '200': + description: 更新后的书签 + content: + application/json: + schema: + $ref: '#/components/schemas/BookmarkResponse' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 书签不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '500': + $ref: '#/components/responses/ServerInternalError' + + delete: + summary: 删除书签 + description: 删除指定id的书签 + operationId: deleteBookmark + tags: [data] + responses: + '204': + description: 删除成功 + '404': + description: 书签不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '500': + $ref: '#/components/responses/ServerInternalError' + +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-BookMark-Token + responses: + ServerInternalError: + description: 服务器内部错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Unauthorized: + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Forbidden: + description: 无权限 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + schemas: + BookmarkRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: 书签名称 + example: 测试名称 + link: + type: string + description: 书签链接 + example: /swagger/index.html + detail: + type: string + description: 书签详情链接 + description: + type: string + description: 书签描述 + required: + - name + + BookmarkResponse: + type: object + description: 书签相应结构体 + properties: + id: + type: integer + format: int64 + description: 书签ID + name: + type: string + description: 书签名称 + link: + type: string + description: 书签链接 + detail: + type: string + description: 书签详情链接 + description: + type: string + description: 书签描述 + created_at: + type: string + format: date-time + description: 创建时间 + updated_at: + type: string + format: date-time + description: 更新时间 + required: + - id + - name + - created_at + - updated_at + + Error: + type: object + description: 错误信息 + properties: + errtype: + type: string + example: "ParameterError" + description: 错误类型 + message: + example: "传递的第一个参数错误" + type: string + description: 错误信息 + required: + - errtype + - message diff --git a/src/main/resources/config/bookmark/client.yaml b/src/main/resources/config/bookmark/client.yaml new file mode 100644 index 0000000..18358a5 --- /dev/null +++ b/src/main/resources/config/bookmark/client.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: ... +package: client +generate: + client: true + models: true +output: ./gen/bookmarks_client/client.go diff --git a/src/main/resources/config/bookmark/server.yaml b/src/main/resources/config/bookmark/server.yaml new file mode 100644 index 0000000..d8531a7 --- /dev/null +++ b/src/main/resources/config/bookmark/server.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: ... +package: server +generate: + gin-server: true + models: true + strict-server: true +output: ./gen/bookmarks_server/server.go diff --git a/src/main/resources/config/user_np/server.yaml b/src/main/resources/config/user_np/server.yaml new file mode 100644 index 0000000..4db1e65 --- /dev/null +++ b/src/main/resources/config/user_np/server.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: ... +package: server +generate: + gin-server: true + models: true + strict-server: true +output: ./gen/user_np_server/server.go diff --git a/src/main/resources/config/user_np/user_np.yaml b/src/main/resources/config/user_np/user_np.yaml new file mode 100644 index 0000000..d7a5000 --- /dev/null +++ b/src/main/resources/config/user_np/user_np.yaml @@ -0,0 +1,210 @@ +openapi: '3.0.3' +info: + title: zzyxyz_user_np_api + description: 用户节点权限相关操作(user_name and password) + version: '1.0' +servers: + - url: http://localhost:8081/api + description: 开发环境 + - url: https://api.zzyxyz.com/api + description: 生产环境 +security: + - ApiKeyAuth: [] + +paths: + /auth/user/{username}/login: + parameters: + - name: username + in: path + example: user_name + required: true + schema: + type: string + post: + summary: 用户登录 + description: 使用用户名和密码进行登录 + operationId: UserLogin + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequest' + responses: + '200': + description: 登录成功 + content: + application/json: + schema: + $ref: '#/components/schemas/LoginResponse' + '400': + description: 请求参数错误 + '401': + description: 认证失败 + + /auth/user/{username}: + parameters: + - name: username + in: path + example: user_name + required: true + schema: + type: string + + post: + summary: 用户注册 + description: 创建新用户账户 + operationId: UserRegister + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterRequest' + responses: + '201': + description: 注册成功 + '400': + description: 请求参数错误 + '409': + description: 用户名已存在 + + patch: + summary: 修改密码 + description: 修改已登录用户的密码 + operationId: updatePassword + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChangePasswordRequest' + responses: + '200': + description: 密码修改成功 + '400': + description: 请求参数错误 + '401': + description: 认证失败 + + delete: + summary: 删除用户 + description: 删除用户 + operationId: deleteUser + security: + - ApiKeyAuth: [] + responses: + '200': + description: 用户注销成功 + '401': + description: 认证失败 + + /auth/user/{username}/info: + parameters: + - name: username + in: path + example: user_name + required: true + schema: + type: string + get: + summary: 获取用户信息 + description: 获取用户信息 json object + operationId: getUserInfo + tags: [auth] + responses: + '200': + description: 用户信息 + content: + application/json: + schema: + type: object + '500': + $ref: '#/components/responses/ServerInternalError' + put: + summary: 保存用户信息 + description: 保存用户信息 json object + operationId: saveUserInfo + tags: [auth] + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + '200': + description: 保存成功 + '500': + $ref: '#/components/responses/ServerInternalError' + +components: + responses: + ServerInternalError: + description: 服务器内部错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + schemas: + LoginRequest: + type: object + required: + - password + properties: + password: + type: string + + LoginResponse: + type: object + properties: + token: + type: string + user_id: + type: integer + format: int64 + + RegisterRequest: + type: object + required: + - password + properties: + password: + type: string + email: + type: string + + ChangePasswordRequest: + type: object + required: + - old_password + - new_password + properties: + old_password: + type: string + new_password: + type: string + + Error: + type: object + description: 错误信息 + properties: + errtype: + type: string + example: "ParameterError" + description: 错误类型 + message: + example: "传递的第一个参数错误" + type: string + description: 错误信息 + required: + - errtype + - message + + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: Authorization diff --git a/src/main/resources/config/vfs/client.yaml b/src/main/resources/config/vfs/client.yaml new file mode 100644 index 0000000..7bc917f --- /dev/null +++ b/src/main/resources/config/vfs/client.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: ... +package: client +generate: + client: true + models: true +output: ./gen/vfs_client/client.go diff --git a/src/main/resources/config/vfs/server.yaml b/src/main/resources/config/vfs/server.yaml new file mode 100644 index 0000000..209fc5e --- /dev/null +++ b/src/main/resources/config/vfs/server.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: ... +package: server +generate: + gin-server: true + models: true + strict-server: true +output: ./gen/vfs_server/server.go diff --git a/src/main/resources/config/vfs/vfs.yaml b/src/main/resources/config/vfs/vfs.yaml new file mode 100644 index 0000000..1bc40dd --- /dev/null +++ b/src/main/resources/config/vfs/vfs.yaml @@ -0,0 +1,375 @@ +openapi: '3.0.3' +info: + title: zzyxyz_vfs_api + description: 虚拟文件系统API服务 + version: '1.0' +servers: + - url: http://localhost:8080/api + description: 开发环境 + - url: https://api.zzyxyz.com/api + description: 生产环境 +tags: + - name: vfs + description: 虚拟文件系统相关操作 + - name: user + description: 用户相关操作 +security: + - ApiKeyAuth: [] + +paths: + /vfs/v1/users/{username}: + parameters: + - name: username + in: path + example: user + required: true + schema: + type: string + + post: + summary: 创建用户 + description: 创建一个用户 + operationId: createUser + tags: [user] + responses: + '201': + description: 创建成功 + headers: + X-VFS-Token: + schema: + type: string + description: 认证令牌 + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '500': + $ref: '#/components/responses/ServerInternalError' + + delete: + summary: 删除用户 + description: 删除一个用户 + operationId: deleteUser + tags: [user] + responses: + '204': + description: 删除成功 + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '500': + $ref: '#/components/responses/ServerInternalError' + + /vfs/v1/files: + parameters: + - name: path + in: query + required: true + description: | + 文件系统路径,例如: + - "/documents/readme.txt" (文件路径) + - "/services/sql.bk.api" (服务文件) + - "/folder/" (目录路径,以/结尾) + schema: + type: string + example: "/documents/readme.txt" + + get: + summary: 读取文件或列出目录 + description: 获取指定路径的文件内容或目录列表 + operationId: getVFSNode + tags: [vfs] + parameters: + - name: op + in: query + required: false + description: 操作类型, list表示列出目录内容, 不指定则读取文件内容 + schema: + type: string + enum: [list] + responses: + '200': + description: 文件内容或目录列表 + content: + application/json: + schema: + description: 目录条目列表 + type: array + items: + $ref: '#/components/schemas/VFSDirectoryEntry' + text/plain: + schema: + type: string + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/PathNotFoundError' + '500': + $ref: '#/components/responses/ServerInternalError' + + post: + summary: 创建文件或目录 + description: 创建文件或目录 + operationId: createVFSNode + tags: [vfs] + requestBody: + required: false + content: + text/plain: + schema: + type: string + description: 文件内容, 或者服务内容 + responses: + '201': + description: 创建成功 + content: + text/plain: + schema: + type: string + application/json: + schema: + $ref: '#/components/schemas/VFSNodeResponse' + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/PathNotFoundError' + '500': + $ref: '#/components/responses/ServerInternalError' + + patch: + summary: 修改文件或修改目录 + description: 修改/移动/重命名/复制 已存在的 文件/目录 + operationId: updateVFSNode + tags: [vfs] + parameters: + - name: op + in: query + description: | + 更新操作模式: + - move: 移动文件或目录到新位置 + - rename: 重命名文件或目录 + - change: 修改文件内容或目录属性 + - copy: 复制文件或目录到新位置 + required: true + schema: + type: string + enum: [move, rename, change, copy] + example: "rename" + requestBody: + required: true + content: + application/json: + schema: + type: string + description: 目的文件夹路径 / 文件名路径 + example: "/home/" + text/plain: + schema: + type: string + responses: + '200': + description: 操作成功 + content: + application/json: + schema: + $ref: '#/components/schemas/VFSNodeResponse' + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/PathNotFoundError' + '500': + $ref: '#/components/responses/ServerInternalError' + + delete: + summary: 删除文件或目录 + description: 删除指定路径的文件或目录 + operationId: deleteVFSNode + tags: [vfs] + parameters: + - name: op + in: query + description: | + 删除操作模式: + - recursive: 递归删除目录及其所有内容 + - force: 强制删除,忽略只读等保护属性 + 不指定时执行普通删除操作 + required: false + schema: + type: string + enum: [recursive, force] + example: "recursive" + responses: + '204': + description: 删除成功 + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/PathNotFoundError' + '500': + $ref: '#/components/responses/ServerInternalError' + +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-VFS-Token + + responses: + ServerInternalError: + description: 服务器内部错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + errtype: "InternalServerError" + message: "服务器内部错误" + + UnauthorizedError: + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + errtype: "Unauthorized" + message: "访问被拒绝,缺少有效的认证令牌" + + ForbiddenError: + description: 禁止 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + errtype: "Forbidden" + message: "访问被拒绝,您没有权限访问此资源" + + PathNotFoundError: + description: 路径不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + errtype: "PathNotFound" + message: "指定的路径不存在" + + ParameterError: + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + errtype: "ParameterError" + message: "请求参数无效或缺失" + + schemas: + VFSNodeType: + type: string + description: 节点类型 + enum: [file, directory, service] + + VFSNodeResponse: + type: object + properties: + name: + type: string + description: 节点名称 + type: + $ref: '#/components/schemas/VFSNodeType' + created_at: + type: string + format: date-time + description: 创建时间 + updated_at: + type: string + format: date-time + description: 更新时间 + required: + - name + - type + - created_at + - updated_at + + VFSDirectoryEntry: + type: object + properties: + name: + type: string + description: 条目名称 + type: + $ref: '#/components/schemas/VFSNodeType' + permissions: + type: string + description: 权限信息,如 "rwo" (读 写 拥有) + required: + - name + - type + + Error: + type: object + description: 错误信息 + properties: + errtype: + type: string + description: | + 错误类型,可能的值包括: + - "InternalServerError": 服务器内部错误 + - "Unauthorized": 客户端需要提供正确格式权限 + - "Forbidden": 无权限访问 + - "PathNotFound": 路径不存在 + - "ParameterError": 请求参数错误 + - "ConflictError": 资源冲突 + - "DatabaseError": 数据库操作错误 + - "NotFoundError": 资源未找到 + - "AccessDenied": 访问被拒绝 + - "ServiceProxyError": 代理服务错误 + example: "InternalServerError" + enum: + - "InternalServerError" + - "Unauthorized" + - "Forbidden" + - "PathNotFound" + - "ParameterError" + - "ConflictError" + - "DatabaseError" + - "NotFoundError" + - "AccessDenied" + - "ServiceProxyError" + message: + type: string + description: 详细的错误信息 + example: "传递的第一个参数错误" + required: + - errtype + - message