การเลือกเครื่องมือที่เหมาะสมสำหรับวัตถุประสงค์ที่ถูกต้องนั้นไปได้ไกลโดยเฉพาะอย่างยิ่งเมื่อต้องสร้างเว็บแอปพลิเคชันสมัยใหม่ พวกเราหลายคนคุ้นเคยกับ AngularJS และการพัฒนาส่วนหน้าเว็บแอปพลิเคชันที่มีประสิทธิภาพนั้นง่ายเพียงใด แม้ว่าหลายคนจะโต้แย้งการใช้เว็บเฟรมเวิร์กยอดนิยมนี้ แต่ก็มีข้อเสนอมากมายและสามารถเป็นได้ ทางเลือกที่เหมาะสม สำหรับความต้องการที่หลากหลาย ในทางกลับกันส่วนประกอบที่คุณใช้ในส่วนหลังจะกำหนดประสิทธิภาพของเว็บแอปพลิเคชันเป็นอย่างมากเนื่องจากส่วนประกอบเหล่านี้มีอิทธิพลต่อประสบการณ์ของผู้ใช้โดยรวม เล่น เป็นเว็บเฟรมเวิร์กความเร็วสูงสำหรับ Java และ Scala มันขึ้นอยู่กับสถาปัตยกรรมน้ำหนักเบาไร้สถานะเป็นมิตรกับเว็บและเป็นไปตามรูปแบบ MVC และหลักการที่คล้ายกับ Rails และ Django
ในบทความนี้เราจะมาดูวิธีที่เราสามารถใช้ AngularJS และ Play เพื่อสร้างแอปพลิเคชันบล็อกง่ายๆด้วยกลไกการตรวจสอบสิทธิ์พื้นฐานและความสามารถในการโพสต์และความคิดเห็น การพัฒนา AngularJS , กับบางอย่าง Twitter Bootstrap สารพัดจะช่วยให้เราสามารถขับเคลื่อนประสบการณ์การใช้งานแอปพลิเคชันแบบหน้าเดียวบนแบ็คเอนด์ REST API ที่ใช้ Play
แอพ AngularJS และ Play จะอยู่ในไดเรกทอรีไคลเอนต์และเซิร์ฟเวอร์ตามนั้น ในตอนนี้เราจะสร้างไดเร็กทอรี 'ไคลเอนต์'
mkdir -p blogapp/client
ในการสร้างโครงกระดูกแอปพลิเคชัน AngularJS เราจะใช้ Yeoman ซึ่งเป็นเครื่องมือนั่งร้านที่น่าทึ่ง การติดตั้ง Yeoman เป็นเรื่องง่าย . การใช้มันเพื่อนั่งร้านแอปพลิเคชั่น AngularJS โครงกระดูกที่เรียบง่ายอาจง่ายกว่า:
cd blogapp/client yo angular
การรันคำสั่งที่สองจะตามมาด้วยตัวเลือกสองสามตัวที่คุณต้องเลือก สำหรับโครงการนี้เราไม่จำเป็นต้องมี“ Sass (with Compass)” เราจะต้องใช้ Boostrap พร้อมกับปลั๊กอิน AngularJS ต่อไปนี้:
ณ จุดนี้เมื่อคุณเสร็จสิ้นการเลือกของคุณคุณจะเริ่มเห็นเอาต์พุต NPM และ Bower บนเทอร์มินัลของคุณ เมื่อการดาวน์โหลดเสร็จสมบูรณ์และติดตั้งแพ็กเกจแล้วคุณจะมีโครงกระดูกแอปพลิเคชัน AngularJS พร้อมใช้งาน
วิธีการอย่างเป็นทางการในการสร้างแอปพลิเคชัน Play ใหม่เกี่ยวข้องกับการใช้เครื่องมือ typesafe Activator ก่อนที่จะใช้งานได้คุณต้องดาวน์โหลดและติดตั้งลงในคอมพิวเตอร์ของคุณ หากคุณใช้ Mac OS และใช้ Homebrew คุณสามารถติดตั้งเครื่องมือนี้ด้วยคำสั่งบรรทัดเดียว:
brew install typesafe-activator
การสร้างแอปพลิเคชัน Play จากบรรทัดคำสั่งนั้นง่ายมาก:
cd blogapp/ activator new server play-java cd server/
ในการนำเข้าแอปพลิเคชันใน IDE เช่น Eclipse หรือ IntelliJ คุณต้อง 'eclipsify' หรือ 'ทำให้เป็นอุดมคติ' แอปพลิเคชันของคุณ โดยเรียกใช้คำสั่งต่อไปนี้:
activator
เมื่อคุณเห็นพรอมต์ใหม่ให้พิมพ์“ eclipse” หรือ“ idea” แล้วกด Enter เพื่อเตรียมรหัสแอปพลิเคชันสำหรับ Eclipse หรือ IntelliJ ตามลำดับ
เพื่อความกระชับเราจะกล่าวถึงขั้นตอนการนำเข้าโปรเจ็กต์ไปยัง IntelliJ ในบทความนี้เท่านั้น กระบวนการอิมพอร์ตไปยัง Eclipse ควรจะง่ายพอ ๆ กัน ในการนำเข้าโปรเจ็กต์ไปยัง IntelliJ ให้เริ่มต้นด้วยการเปิดใช้งานตัวเลือก“ โปรเจ็กต์จากแหล่งที่มาที่มีอยู่…” ที่อยู่ใน“ ไฟล์ -> ใหม่” จากนั้นเลือกไฟล์ build.sbt ของคุณแล้วคลิก“ ตกลง” เมื่อคลิก“ ตกลง” อีกครั้งในกล่องโต้ตอบถัดไป IntelliJ ควรเริ่มนำเข้าแอปพลิเคชัน Play ของคุณเป็นโครงการ SBT
typesafe Activator ยังมาพร้อมกับอินเทอร์เฟซผู้ใช้แบบกราฟิกซึ่งคุณสามารถใช้เพื่อ สร้างรหัสแอปพลิเคชันโครงกระดูกนี้ .
ตอนนี้เราได้นำเข้าแอปพลิเคชัน Play ของเราไปยัง IntelliJ แล้วเราควรนำเข้าแอปพลิเคชัน AngularJS ของเราไปยังพื้นที่ทำงานด้วย เราสามารถนำเข้าเป็นโครงการแยกต่างหากหรือเป็นโมดูลไปยังโครงการที่มีอยู่ซึ่งแอปพลิเคชัน Play อาศัยอยู่
ที่นี่เราจะนำเข้าแอปพลิเคชัน Angular เป็นโมดูล ภายใต้เมนู“ ไฟล์” เราจะเลือกตัวเลือก“ ใหม่ -> โมดูลจากแหล่งที่มาที่มีอยู่…” จากกล่องโต้ตอบเราจะเลือกไดเร็กทอรี 'ไคลเอนต์' และคลิกที่ 'ตกลง' ในสองหน้าจอถัดไปให้คลิกที่“ ถัดไป” และ“ เสร็จสิ้น” ตามลำดับ
ณ จุดนี้ควรเริ่มแอปพลิเคชัน AngularJS เป็นงาน Grunt จาก IDE ได้ ขยายโฟลเดอร์ไคลเอนต์ของคุณแล้วคลิกขวาที่ Gruntfile.js ในเมนูป๊อปอัพให้เลือก“ Show Grunt Tasks” แผงที่มีข้อความ 'Grunt' จะปรากฏขึ้นพร้อมรายการงาน:
ในการเริ่มให้บริการแอปพลิเคชันให้ดับเบิลคลิกที่“ ให้บริการ” สิ่งนี้ควรเปิดเว็บเบราว์เซอร์เริ่มต้นของคุณทันทีและชี้ไปที่ที่อยู่ localhost คุณควรเห็นหน้า AngularJS ที่มีรูปทรงที่มีโลโก้ของ Yeoman อยู่
ต่อไปเราต้องเปิดแอปพลิเคชันเซิร์ฟเวอร์ส่วนหลังของเรา ก่อนที่เราจะดำเนินการต่อเราต้องแก้ไขปัญหาสองสามประการ:
ในการแก้ไขปัญหาทั้งสองนี้สิ่งที่เราต้องทำคือใช้ Grunt proxy เพื่อให้คำขอ AJAX ทั้งหมดไปยังแอปพลิเคชัน Play เป็นพร็อกซี ด้วยเหตุนี้โดยพื้นฐานแล้วแอ็พพลิเคชันเซิร์ฟเวอร์ทั้งสองนี้จะพร้อมใช้งานที่หมายเลขพอร์ตเดียวกัน
ก่อนอื่นให้เราเปลี่ยนหมายเลขพอร์ตของแอปพลิเคชันเซิร์ฟเวอร์ Play เป็น 9090 ในการดำเนินการนี้ให้เปิดหน้าต่าง“ Run / Debug Configurations” โดยคลิกที่“ Run -> Edit Configurations” จากนั้นเปลี่ยนหมายเลขพอร์ตในช่อง 'URL To Open' คลิกที่“ ตกลง” เพื่ออนุมัติการเปลี่ยนแปลงนี้และปิดหน้าต่าง การคลิกที่ปุ่ม“ เรียกใช้” ควรเริ่มกระบวนการแก้ไขการอ้างอิง - บันทึกของกระบวนการนี้จะเริ่มปรากฏขึ้น
เมื่อเสร็จแล้วคุณสามารถไปที่ http: // localhost: 9090 บนเว็บเบราว์เซอร์ของคุณและในไม่กี่วินาทีคุณจะสามารถเห็นแอปพลิเคชัน Play ของคุณ ในการกำหนดค่า Grunt proxy ก่อนอื่นเราต้องติดตั้งแพ็คเกจ Node.js ขนาดเล็กโดยใช้ NPM:
cd blogapp/client npm install grunt-connect-proxy --save-dev
ต่อไปเราต้องปรับแต่ง Gruntfile.js ของเรา ในไฟล์นั้นให้ค้นหางาน 'เชื่อมต่อ' และแทรกคีย์ / ค่า 'พร็อกซี' ไว้ด้านหลัง:
proxies: [ { context: '/app', // the context of the data service host: 'localhost', // wherever the data service is running port: 9090, // the port that the data service is running on changeOrigin: true } ],
ตอนนี้ Grunt จะพร็อกซีคำขอทั้งหมดไปยัง“ / app / *” ไปยังแอปพลิเคชัน Back-end Play วิธีนี้จะช่วยให้เราไม่ต้องอยู่ในรายการที่อนุญาตพิเศษทุกครั้งที่โทรไปยังส่วนหลัง นอกจากนี้เรายังต้องปรับแต่งพฤติกรรม livereload ของเรา:
livereload: { options: { open: true, middleware: function (connect) { var middlewares = []; // Setup the proxy middlewares.push(require('grunt-connect-proxy/lib/utils').proxyRequest); // Serve static files middlewares.push(connect.static('.tmp')); middlewares.push(connect().use( '/bower_components', connect.static('./bower_components') )); middlewares.push(connect().use( '/app/styles', connect.static('./app/styles') )); middlewares.push(connect.static(appConfig.app)); return middlewares; } } },
ในที่สุดเราจำเป็นต้องเพิ่มการพึ่งพาใหม่ '' configureProxies: server 'ให้กับงาน' ให้บริการ ':
grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { if (target === 'dist') { return grunt.task.run(['build', 'connect:dist:keepalive']); } grunt.task.run([ 'clean:server', 'wiredep', 'concurrent:server', 'autoprefixer:server', 'configureProxies:server', 'connect:livereload', 'watch' ]); });
เมื่อรีสตาร์ท Grunt คุณควรสังเกตบรรทัดต่อไปนี้ในบันทึกของคุณที่ระบุว่าพร็อกซีกำลังทำงาน:
Running 'autoprefixer:server' (autoprefixer) task File .tmp/styles/main.css created. Running 'configureProxies:server' (configureProxies) task Running 'connect:livereload' (connect) task Started connect web server on http://localhost:9000
เราจะเริ่มต้นด้วยการสร้างแบบฟอร์มลงทะเบียนสำหรับแอปพลิเคชันบล็อกของเรา นอกจากนี้ยังช่วยให้เราตรวจสอบได้ว่าทุกอย่างทำงานตามที่ควร เราสามารถใช้ Yeoman เพื่อสร้างตัวควบคุมการสมัครและดูใน AngularJS:
yo angular:controller signup yo angular:view signup
ต่อไปเราควรอัปเดตการกำหนดเส้นทางของแอปพลิเคชันของเราเพื่ออ้างอิงมุมมองที่สร้างขึ้นใหม่นี้และลบตัวควบคุมและมุมมอง 'เกี่ยวกับ' ที่สร้างขึ้นโดยอัตโนมัติซ้ำซ้อน จากภายในไฟล์“ app / scripts / app.js” ให้ลบการอ้างอิงถึง“ app / scripts / controllers / about.js” และ“ app / views / about.html” ออกโดยให้:
.config(function ($routeProvider) { $routeProvider .when('/', { templateUrl: 'views/main.html', controller: 'MainCtrl' }) .when('/signup', { templateUrl: 'views/signup.html', controller: 'SignupCtrl' }) .otherwise({ redirectTo: '/' });
ในทำนองเดียวกันให้อัปเดตไฟล์“ app / index.html” เพื่อลบลิงก์ที่ซ้ำซ้อนและเพิ่มลิงก์ไปยังหน้าลงชื่อสมัครใช้:
นอกจากนี้ให้ลบแท็กสคริปต์สำหรับ“ about.js”:
Email Password Sign up!
จากนั้นเพิ่มแบบฟอร์มในไฟล์“ signup.html” ของเรา:
angular.module('clientApp') .controller('SignupCtrl', function ($scope, $http, $log) { $scope.signup = function() { var payload = { email : $scope.email, password : $scope.password }; $http.post('app/signup', payload) .success(function(data) { $log.debug(data); }); }; });
เราจำเป็นต้องทำให้แบบฟอร์มถูกประมวลผลโดยตัวควบคุมเชิงมุม เป็นที่น่าสังเกตว่าเราไม่จำเป็นต้องเพิ่มแอตทริบิวต์ 'ng-controller' โดยเฉพาะในมุมมองของเราเนื่องจากตรรกะการกำหนดเส้นทางของเราใน 'app.js' จะทำให้ตัวควบคุมทำงานโดยอัตโนมัติก่อนที่มุมมองของเราจะโหลด สิ่งที่เราต้องทำเพื่อวางสายแบบฟอร์มนี้คือมีฟังก์ชัน 'สมัครใช้งาน' ที่เหมาะสมซึ่งกำหนดไว้ใน $ scope ควรทำในไฟล์“ signup.js”:
public static Result signup() { return ok('Success!'); }
ตอนนี้ให้เราเปิดคอนโซลนักพัฒนาซอฟต์แวร์ Chrome เปลี่ยนไปใช้แท็บ 'เครือข่าย' แล้วลองส่งแบบฟอร์มการสมัคร
เราจะเห็นว่า Play back-end ตอบกลับโดยธรรมชาติพร้อมกับหน้าแสดงข้อผิดพลาด 'ไม่พบการดำเนินการ' คาดว่าจะยังไม่ได้ดำเนินการ แต่ความหมายก็คือการตั้งค่าพร็อกซี Grunt ของเราทำงานได้อย่างถูกต้อง!
ต่อไปเราจะเพิ่ม“ การดำเนินการ” ซึ่งโดยพื้นฐานแล้วเป็นวิธีการในตัวควบคุมแอปพลิเคชัน Play ในคลาส 'แอปพลิเคชัน' ในแพ็กเกจ 'แอป / คอนโทรลเลอร์' ให้เพิ่มวิธีการใหม่ 'สมัครใช้งาน':
POST /app/signup controllers.Application.signup
ตอนนี้เปิดไฟล์“ conf / route” และเพิ่มบรรทัดต่อไปนี้:
db.default.driver=org.h2.Driver db.default.url='jdbc:h2:mem:play' db.default.user=sa db.default.password='' ... ebean.default='models.*'
สุดท้ายเรากลับไปที่เว็บเบราว์เซอร์ http: // localhost: 9000 / # / signup การคลิกปุ่ม 'ส่ง' ในครั้งนี้ควรให้สิ่งที่แตกต่างออกไป:
คุณควรจะเห็นค่าฮาร์ดโค้ดที่ส่งกลับซึ่งเป็นค่าที่เราเขียนในวิธีการลงทะเบียน หากเป็นเช่นนั้นเราก็พร้อมที่จะดำเนินการต่อเนื่องจากสภาพแวดล้อมการพัฒนาของเราพร้อมและกำลังทำงานสำหรับทั้งแอปพลิเคชัน Angular และ Play
ก่อนกำหนดโมเดลให้เราเลือกพื้นที่เก็บข้อมูลก่อน ในบทความนี้เราจะใช้ฐานข้อมูล H2 ในหน่วยความจำ ในการเปิดใช้งานให้ค้นหาและยกเลิกการใส่ข้อคิดเห็นบรรทัดต่อไปนี้ในไฟล์“ application.conf”:
applyEvolutions.default=true
และเพิ่มบรรทัดต่อไปนี้:
อะไรทำให้เกิดวิกฤตหนี้กรีซ
// User.java @Entity public class User extends Model { @Id public Long id; @Column(length = 255, unique = true, nullable = false) @Constraints.MaxLength(255) @Constraints.Required @Constraints.Email public String email; @Column(length = 64, nullable = false) private byte[] shaPassword; @OneToMany(cascade = CascadeType.ALL) @JsonIgnore public List posts; public void setPassword(String password) { this.shaPassword = getSha512(password); } public void setEmail(String email) { this.email = email.toLowerCase(); } public static final Finder find = new Finder( Long.class, User.class); public static User findByEmailAndPassword(String email, String password) { return find .where() .eq('email', email.toLowerCase()) .eq('shaPassword', getSha512(password)) .findUnique(); } public static User findByEmail(String email) { return find .where() .eq('email', email.toLowerCase()) .findUnique(); } public static byte[] getSha512(String value) { try { return MessageDigest.getInstance('SHA-512').digest(value.getBytes('UTF-8')); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } }
รูปแบบโดเมนบล็อกของเราค่อนข้างเรียบง่าย ก่อนอื่นเรามีผู้ใช้ที่สามารถสร้างโพสต์จากนั้นแต่ละโพสต์สามารถแสดงความคิดเห็นได้โดยผู้ใช้ที่ลงชื่อเข้าใช้ มาสร้างแบบจำลอง Ebean ของเรากัน
// BlogPost.java @Entity public class BlogPost extends Model { @Id public Long id; @Column(length = 255, nullable = false) @Constraints.MaxLength(255) @Constraints.Required public String subject; @Column(columnDefinition = 'TEXT') @Constraints.Required public String content; @ManyToOne public User user; public Long commentCount; @OneToMany(cascade = CascadeType.ALL) public List comments; public static final Finder find = new Finder( Long.class, BlogPost.class); public static List findBlogPostsByUser(final User user) { return find .where() .eq('user', user) .findList(); } public static BlogPost findBlogPostById(final Long id) { return find .where() .eq('id', id) .findUnique(); } }
// PostComment.java @Entity public class PostComment extends Model { @Id public Long id; @ManyToOne @JsonIgnore public BlogPost blogPost; @ManyToOne public User user; @Column(columnDefinition = 'TEXT') public String content; public static final Finder find = new Finder( Long.class, PostComment.class); public static List findAllCommentsByPost(final BlogPost blogPost) { return find .where() .eq('post', blogPost) .findList(); } public static List findAllCommentsByUser(final User user) { return find .where() .eq('user', user) .findList(); } }
// Application.java public static Result signup() { Form signUpForm = Form.form(SignUp.class).bindFromRequest(); if ( signUpForm.hasErrors()) { return badRequest(signUpForm.errorsAsJson()); } SignUp newUser = signUpForm.get(); User existingUser = User.findByEmail(newUser.email); if(existingUser != null) { return badRequest(buildJsonResponse('error', 'User exists')); } else { User user = new User(); user.setEmail(newUser.email); user.setPassword(newUser.password); user.save(); session().clear(); session('username', newUser.email); return ok(buildJsonResponse('success', 'User created successfully')); } } public static class UserForm { @Constraints.Required @Constraints.Email public String email; } public static class SignUp extends UserForm { @Constraints.Required @Constraints.MinLength(6) public String password; } private static ObjectNode buildJsonResponse(String type, String message) { ObjectNode wrapper = Json.newObject(); ObjectNode msg = Json.newObject(); msg.put('message', message); wrapper.put(type, msg); return wrapper; }
ตอนนี้เรามาสร้างการกระทำจริงครั้งแรกของเราโดยให้ผู้ใช้ลงทะเบียน:
yo angular:service alerts
โปรดทราบว่าการรับรองความถูกต้องที่ใช้ในแอพนี้เป็นพื้นฐานมากและไม่แนะนำให้ใช้ในการผลิต
ส่วนที่น่าสนใจคือเรากำลังใช้แบบฟอร์ม Play เพื่อจัดการกับแบบฟอร์มการสมัคร เรากำหนดข้อ จำกัด สองสามข้อในคลาสฟอร์มการสมัครของเรา การตรวจสอบความถูกต้องจะดำเนินการให้เราโดยอัตโนมัติโดยไม่จำเป็นต้องใช้ตรรกะการตรวจสอบที่ชัดเจน
หากเรากลับไปที่แอปพลิเคชัน AngularJS ในเว็บเบราว์เซอร์และคลิกที่“ ส่ง” อีกครั้งเราจะเห็นว่าเซิร์ฟเวอร์ตอบสนองด้วยข้อผิดพลาดที่เหมาะสมซึ่งจำเป็นต้องกรอกข้อมูลในช่องเหล่านี้
ดังนั้นเราจึงได้รับข้อผิดพลาดจากเซิร์ฟเวอร์ แต่ผู้ใช้แอปพลิเคชันไม่รู้ว่าเกิดอะไรขึ้น อย่างน้อยที่สุดที่เราทำได้คือแสดงข้อผิดพลาดให้กับผู้ใช้ของเรา ตามหลักการแล้วเราจำเป็นต้องเข้าใจว่าเราได้รับข้อผิดพลาดประเภทใดและแสดงข้อความที่ใช้งานง่าย มาสร้างบริการแจ้งเตือนง่ายๆที่จะช่วยให้เราแสดงข้อผิดพลาด
ขั้นแรกเราต้องสร้างเทมเพลตบริการด้วย Yeoman:
angular.module('clientApp') .factory('alertService', function($timeout) { var ALERT_TIMEOUT = 5000; function add(type, msg, timeout) { if (timeout) { $timeout(function(){ closeAlert(this); }, timeout); } else { $timeout(function(){ closeAlert(this); }, ALERT_TIMEOUT); } return alerts.push({ type: type, msg: msg, close: function() { return closeAlert(this); } }); } function closeAlert(alert) { return closeAlertIdx(alerts.indexOf(alert)); } function closeAlertIdx(index) { return alerts.splice(index, 1); } function clear(){ alerts = []; } function get() { return alerts; } var service = { add: add, closeAlert: closeAlert, closeAlertIdx: closeAlertIdx, clear: clear, get: get }, alerts = []; return service; } );
จากนั้นเพิ่มรหัสนี้ใน“ alerts.js”:
yo angular:controller alerts
ตอนนี้เรามาสร้างตัวควบคุมแยกต่างหากที่รับผิดชอบการแจ้งเตือน:
angular.module('clientApp') .controller('AlertsCtrl', function ($scope, alertService) { $scope.alerts = alertService.get(); });
bower install angular-bootstrap --save
ตอนนี้เราต้องแสดงข้อความแสดงข้อผิดพลาด Bootstrap ที่ดีจริงๆ วิธีที่ง่ายที่สุดคือการใช้ UI เชิงมุม . เราสามารถใช้ Bower เพื่อติดตั้ง:
angular .module('clientApp', [ 'ngAnimate', 'ngCookies', 'ngResource', 'ngRoute', 'ngSanitize', 'ngTouch', 'ui.bootstrap' ])
ใน“ app.js” ต่อท้ายโมดูล Angular UI:
{{ alert.msg }}
มาเพิ่มคำสั่งการแจ้งเตือนในไฟล์“ index.html” ของเรา:
วิธีการเขียนเอกสารทางเทคนิคสำหรับซอฟต์แวร์
angular.module('clientApp') .controller('SignupCtrl', function ($scope, $http, $log, alertService, $location, userService) { $scope.signup = function() { var payload = { email : $scope.email, password : $scope.password }; $http.post('app/signup', payload) .error(function(data, status) { if(status === 400) { angular.forEach(data, function(value, key) { if(key === 'email' || key === 'password') { alertService.add('danger', key + ' : ' + value); } else { alertService.add('danger', value.message); } }); } if(status === 500) { alertService.add('danger', 'Internal server error!'); } }) }; });
ในที่สุดเราต้องอัปเดตตัวควบคุมการสมัคร:
yo angular:view dashboard yo angular:controller dashboard
ตอนนี้ถ้าเราส่งแบบฟอร์มเปล่าอีกครั้งเราจะเห็นข้อผิดพลาดปรากฏเหนือแบบฟอร์ม:
ขณะนี้ข้อผิดพลาดได้รับการจัดการแล้วเราจำเป็นต้องดำเนินการบางอย่างเมื่อการสมัครผู้ใช้สำเร็จ เราสามารถเปลี่ยนเส้นทางผู้ใช้ไปยังหน้าแดชบอร์ดที่เขาสามารถเพิ่มโพสต์ได้ แต่ก่อนอื่นเราต้องสร้างมัน:
angular.module('clientApp') .controller('SignupCtrl', function ($scope, $http, $log, alertService, $location) { // .. .success(function(data) { if(data.hasOwnProperty('success')) { $location.path('/dashboard'); } });
แก้ไขวิธีการลงทะเบียนตัวควบคุม 'signup.js' เพื่อให้ประสบความสำเร็จจะเปลี่ยนเส้นทางผู้ใช้:
.when('/dashboard', { templateUrl: 'views/dashboard.html', controller: 'DashboardCtrl' })
เพิ่มเส้นทางใหม่ใน“ apps.js”:
yo angular:service user
เราต้องติดตามด้วยว่าผู้ใช้เข้าสู่ระบบหรือไม่ให้เราสร้างบริการแยกต่างหากสำหรับสิ่งนั้น:
// user.js angular.module('clientApp') .factory('userService', function() { var username = ''; return { username : username }; });
.success(function(data) { if(data.hasOwnProperty('success')) { userService.username = $scope.email; $location.path('/dashboard');; } });
และแก้ไขตัวควบคุมการสมัครเพื่อตั้งค่าผู้ใช้เป็นผู้ที่เพิ่งลงทะเบียน:
public static Result login() { Form loginForm = Form.form(Login.class).bindFromRequest(); if (loginForm.hasErrors()) { return badRequest(loginForm.errorsAsJson()); } Login loggingInUser = loginForm.get(); User user = User.findByEmailAndPassword(loggingInUser.email, loggingInUser.password); if(user == null) { return badRequest(buildJsonResponse('error', 'Incorrect email or password')); } else { session().clear(); session('username', loggingInUser.email); ObjectNode wrapper = Json.newObject(); ObjectNode msg = Json.newObject(); msg.put('message', 'Logged in successfully'); msg.put('user', loggingInUser.email); wrapper.put('success', msg); return ok(wrapper); } } public static Result logout() { session().clear(); return ok(buildJsonResponse('success', 'Logged out successfully')); } public static Result isAuthenticated() { if(session().get('username') == null) { return unauthorized(); } else { ObjectNode wrapper = Json.newObject(); ObjectNode msg = Json.newObject(); msg.put('message', 'User is logged in already'); msg.put('user', session().get('username')); wrapper.put('success', msg); return ok(wrapper); } } public static class Login extends UserForm { @Constraints.Required public String password; }
ก่อนที่เราจะเพิ่มฟังก์ชันการทำงานหลักในการเพิ่มโพสต์เรามาดูแลคุณลักษณะที่สำคัญอื่น ๆ เช่นความสามารถในการเข้าสู่ระบบและออกจากระบบการแสดงข้อมูลผู้ใช้บนแดชบอร์ดและการเพิ่มการรองรับการตรวจสอบสิทธิ์ในส่วนหลัง
ไปที่แอปพลิเคชัน Play ของเราและดำเนินการเข้าสู่ระบบและออกจากระบบ เพิ่มบรรทัดเหล่านี้ใน“ Application.java”:
public class Secured extends Security.Authenticator { @Override public String getUsername(Context ctx) { return ctx.session().get('username'); } @Override public Result onUnauthorized(Context ctx) { return unauthorized(); } }
ต่อไปเราจะเพิ่มความสามารถในการอนุญาตการโทรแบ็คเอนด์เฉพาะกับผู้ใช้ที่ตรวจสอบสิทธิ์เท่านั้น สร้าง“ Secured.java” ด้วยรหัสต่อไปนี้:
yo angular:controller menu
เราจะใช้คลาสนี้ในภายหลังเพื่อป้องกันการกระทำใหม่ ๆ ต่อไปเราควรปรับแต่งเมนูหลักของแอปพลิเคชัน AngularJS เพื่อให้แสดงชื่อผู้ใช้และลิงก์ออกจากระบบ ด้วยเหตุนี้เราต้องสร้างตัวควบคุม:
// menu.js angular.module('clientApp') .controller('MenuCtrl', function ($scope, $http, userService, $location) { $scope.user = userService; $scope.logout = function() { $http.get('/app/logout') .success(function(data) { if(data.hasOwnProperty('success')) { userService.username = ''; $location.path('/login'); } }); }; $scope.$watch('user.username', function (newVal) { if(newVal === '') { $scope.isLoggedIn = false; } else { $scope.username = newVal; $scope.isLoggedIn = true; } }); });
yo angular:controller login yo angular:view login
เรายังต้องการมุมมองและตัวควบคุมสำหรับหน้าเข้าสู่ระบบ:
Email Password Log in
// login.js angular.module('clientApp') .controller('LoginCtrl', function ($scope, userService, $location, $log, $http, alertService) { $scope.isAuthenticated = function() { if(userService.username) { $log.debug(userService.username); $location.path('/dashboard'); } else { $http.get('/app/isauthenticated') .error(function() { $location.path('/login'); }) .success(function(data) { if(data.hasOwnProperty('success')) { userService.username = data.success.user; $location.path('/dashboard'); } }); } }; $scope.isAuthenticated(); $scope.login = function() { var payload = { email : this.email, password : this.password }; $http.post('/app/login', payload) .error(function(data, status){ if(status === 400) { angular.forEach(data, function(value, key) { if(key === 'email' || key === 'password') { alertService.add('danger', key + ' : ' + value); } else { alertService.add('danger', value.message); } }); } else if(status === 401) { alertService.add('danger', 'Invalid login or password!'); } else if(status === 500) { alertService.add('danger', 'Internal server error!'); } else { alertService.add('danger', data); } }) .success(function(data){ $log.debug(data); if(data.hasOwnProperty('success')) { userService.username = data.success.user; $location.path('/dashboard'); } }); }; });
Toggle Dropdown
{{ username }} ต่อไปเราจะปรับแต่งเมนูเพื่อให้สามารถแสดงข้อมูลผู้ใช้:
yo angular:view addpost
ตอนนี้หากคุณลงชื่อเข้าใช้แอปพลิเคชันคุณควรจะเห็นหน้าจอต่อไปนี้:
ตอนนี้เรามีกลไกการลงทะเบียนและการตรวจสอบสิทธิ์ขั้นพื้นฐานแล้วเราสามารถลงไปใช้งานฟังก์ชันการโพสต์ได้ มาเพิ่มมุมมองและตัวควบคุมใหม่สำหรับการเพิ่มโพสต์
Subject Post Submit post
yo angular:controller addpost
// addpost.js angular.module('clientApp') .controller('AddpostCtrl', function ($scope, $http, alertService, $location) { $scope.post = function() { var payload = { subject : $scope.subject, content: $scope.content }; $http.post('/app/post', payload) .error(function(data, status) { if(status === 400) { angular.forEach(data, function(value, key) { if(key === 'subject' || key === 'content') { alertService.add('danger', key + ' : ' + value); } else { alertService.add('danger', value.message); } }); } else if(status === 401) { $location.path('/login'); } else if(status === 500) { alertService.add('danger', 'Internal server error!'); } else { alertService.add('danger', data); } }) .success(function(data) { $scope.subject = ''; $scope.content = ''; alertService.add('success', data.success.message); }); }; });
.when('/addpost', { templateUrl: 'views/addpost.html', controller: 'AddpostCtrl' })
จากนั้นเราอัปเดต“ app.js” เพื่อรวม:
ต่อไปเราจะแก้ไข“ index.html” เพื่อเพิ่มลิงค์สำหรับมุมมอง“ addpost” ของเราในเมนูแดชบอร์ด:
// Post.java public class Post extends Controller { public static Result addPost() { Form postForm = Form.form(PostForm.class).bindFromRequest(); if (postForm.hasErrors()) { return badRequest(postForm.errorsAsJson()); } else { BlogPost newBlogPost = new BlogPost(); newBlogPost.commentCount = 0L; newBlogPost.subject = postForm.get().subject; newBlogPost.content = postForm.get().content; newBlogPost.user = getUser(); newBlogPost.save(); } return ok(Application.buildJsonResponse('success', 'Post added successfully')); } private static User getUser() { return User.findByEmail(session().get('username')); } public static class PostForm { @Constraints.Required @Constraints.MaxLength(255) public String subject; @Constraints.Required public String content; } }
ตอนนี้ในฝั่งแอปพลิเคชัน Play มาสร้างตัวควบคุมโพสต์ใหม่ด้วยวิธี addPost:
POST /app/post controllers.Post.addPost
เพิ่มรายการใหม่ในไฟล์เส้นทางเพื่อให้สามารถจัดการกับวิธีการที่เพิ่มใหม่ในการกำหนดเส้นทาง:
// Application.java public static Result getPosts() { return ok(Json.toJson(BlogPost.find.findList())); }
ณ จุดนี้คุณควรจะเพิ่มโพสต์ใหม่ได้
การเพิ่มโพสต์มีคุณค่าเพียงเล็กน้อยหากเราไม่สามารถแสดงได้ สิ่งที่เราต้องการทำคือแสดงรายการโพสต์ทั้งหมดในหน้าหลัก เราเริ่มต้นด้วยการเพิ่มวิธีการใหม่ในตัวควบคุมแอปพลิเคชันของเรา:
GET /app/posts controllers.Application.getPosts
และลงทะเบียนในไฟล์เส้นทางของเรา:
// main.js angular.module('clientApp') .controller('MainCtrl', function ($scope, $http) { $scope.getPosts = function() { $http.get('app/posts') .success(function(data) { $scope.posts = data; }); }; $scope.getPosts(); });
ถัดไปในแอปพลิเคชัน AngularJS ของเราเราปรับเปลี่ยนคอนโทรลเลอร์หลักของเรา:
{{ post.subject }}
{{ post.content }}
Post by: {{ post.user.email }} | Comments {{ post.commentCount }}
สุดท้ายลบทุกอย่างออกจาก“ main.html” และเพิ่มสิ่งนี้:
yo angular:controller viewpost yo angular:view viewpost
ตอนนี้ถ้าคุณโหลดโฮมเพจของแอปพลิเคชันคุณจะเห็นสิ่งที่คล้ายกับสิ่งนี้:
เราควรมีมุมมองแยกต่างหากสำหรับแต่ละโพสต์
// viewpost.js angular.module('clientApp') .controller('ViewpostCtrl', function ($scope, $http, alertService, userService, $location) { $scope.user = userService; $scope.params = $routeParams; $scope.postId = $scope.params.postId; $scope.viewPost = function() { $http.get('/app/post/' + $scope.postId) .error(function(data) { alertService.add('danger', data.error.message); }) .success(function(data) { $scope.post = data; }); }; $scope.viewPost(); });
{{ post.subject }}
{{ post.content }}
Post by: {{ post.user.email }} | Comments {{ post.commentCount }}
app.js: .when('/viewpost/:postId', { templateUrl: 'views/viewpost.html', controller: 'ViewpostCtrl' })
และเส้นทาง AngularJS:
// Application.java public static Result getPost(Long id) { BlogPost blogPost = BlogPost.findBlogPostById(id); if(blogPost == null) { return notFound(buildJsonResponse('error', 'Post not found')); } return ok(Json.toJson(blogPost)); }
เช่นเดิมเราได้เพิ่มวิธีการใหม่ให้กับตัวควบคุมแอปพลิเคชันของเรา:
GET /app/post/:id controllers.Application.getPost(id: Long)
…และเส้นทางใหม่:
// dashboard.js angular.module('clientApp') .controller('DashboardCtrl', function ($scope, $log, $http, alertService, $location) { $scope.loadPosts = function() { $http.get('/app/userposts') .error(function(data, status) { if(status === 401) { $location.path('/login'); } else { alertService.add('danger', data.error.message); } }) .success(function(data) { $scope.posts = data; }); }; $scope.loadPosts(); });
ตอนนี้ถ้าคุณไปที่ http: // localhost: 9000 / # / viewpost / 1 คุณจะสามารถโหลดมุมมองสำหรับโพสต์ที่ต้องการได้ ต่อไปเราจะเพิ่มความสามารถในการดูโพสต์ของผู้ใช้ในแดชบอร์ด:
My Posts
No posts yet. Add a post {{ post.subject }} | Comments {{ post.commentCount }}
// Post.java public static Result getUserPosts() { User user = getUser(); if(user == null) { return badRequest(Application.buildJsonResponse('error', 'No such user')); } return ok(Json.toJson(BlogPost.findBlogPostsByUser(user))); }
เพิ่มวิธีการใหม่ให้กับ Post controller ตามด้วยเส้นทางที่ตรงกับวิธีนี้:
GET /app/userposts controllers.Post.getUserPosts
// Post.java public static Result addComment() { Form commentForm = Form.form(CommentForm.class).bindFromRequest(); if (commentForm.hasErrors()) { return badRequest(commentForm.errorsAsJson()); } else { PostComment newComment = new PostComment(); BlogPost blogPost = BlogPost.findBlogPostById(commentForm.get().postId); blogPost.commentCount++; blogPost.save(); newComment.blogPost = blogPost; newComment.user = getUser(); newComment.content = commentForm.get().comment; newComment.save(); return ok(Application.buildJsonResponse('success', 'Comment added successfully')); } } public static class CommentForm { @Constraints.Required public Long postId; @Constraints.Required public String comment; }
ตอนนี้เมื่อคุณสร้างโพสต์โพสต์เหล่านั้นจะปรากฏบนแดชบอร์ด:
ในการใช้ฟังก์ชันการแสดงความคิดเห็นเราจะเริ่มต้นด้วยการเพิ่มวิธีการใหม่ในตัวควบคุมโพสต์:
POST /app/comment controllers.Post.addComment
และเช่นเคยเราต้องลงทะเบียนเส้นทางใหม่สำหรับวิธีนี้:
$scope.addComment = function() { var payload = { postId: $scope.postId, comment: $scope.comment }; $http.post('/app/comment', payload) .error(function(data, status) { if(status === 400) { angular.forEach(data, function(value, key) { if(key === 'comment') { alertService.add('danger', key + ' : ' + value); } else { alertService.add('danger', value.message); } }); } else if(status === 401) { $location.path('/login'); } else if(status === 500) { alertService.add('danger', 'Internal server error!'); } else { alertService.add('danger', data); } }) .success(function(data) { alertService.add('success', data.success.message); $scope.comment = ''; $scope.viewPost(); }); };
ในแอปพลิเคชัน AngularJS ของเราเราเพิ่มสิ่งต่อไปนี้ใน“ viewpost.js”:
By: {{ comment.user.email }}
{{ comment.content }} Login to comment
Add comment
Comment Add comment
และสุดท้ายเพิ่มบรรทัดต่อไปนี้ใน“ viewpost.html”:
|_+_|
ตอนนี้หากคุณเปิดโพสต์ใด ๆ คุณจะสามารถเพิ่มและดูความคิดเห็นได้
ในบทช่วยสอนนี้เราได้สร้างบล็อก AngularJS ด้วยแอปพลิเคชัน Play ที่ทำหน้าที่เป็นแบ็คเอนด์ REST API แม้ว่าแอปพลิเคชันจะไม่มีการตรวจสอบข้อมูลที่มีประสิทธิภาพ (โดยเฉพาะในฝั่งไคลเอ็นต์) และความปลอดภัย แต่หัวข้อเหล่านี้อยู่นอกขอบเขตของบทช่วยสอนนี้ มีจุดมุ่งหมายเพื่อแสดงให้เห็นถึงหนึ่งในวิธีที่เป็นไปได้มากมายในการสร้างแอปพลิเคชันประเภทนี้ เพื่อความสะดวกซอร์สโค้ดของแอปพลิเคชันนี้ได้รับการอัปโหลดไปยัง ที่เก็บ GitHub .
หากคุณพบว่าการรวมกันของ AngularJS และ Play ในการพัฒนาแอปพลิเคชันบนเว็บน่าสนใจฉันขอแนะนำให้คุณอ่านหัวข้อต่อไปนี้เพิ่มเติม: