开发体育赛事直播体系主播认证功能技术实现方案

打印 上一主题 下一主题

主题 1761|帖子 1761|积分 5283

该体育直播体系体系由东莞梦幻网络科技开发,使用 ThinkPHP 作为后端,Vue.js 作为 PC/H5 端框架,Java 和 Objective-C 分别用于安卓和 iOS 开发
1、前端实现 (Vue.js)

  1. <template>
  2.   <div class="anchor-certification">
  3.     <div class="header">
  4.       <h1>主播认证</h1>
  5.     </div>
  6.    
  7.     <div class="form-container">
  8.       <el-form :model="form" :rules="rules" ref="form" label-width="120px">
  9.         <!-- 直播信息 -->
  10.         <el-form-item label="直播赛事" prop="liveType">
  11.           <el-checkbox-group v-model="form.liveType">
  12.             <el-checkbox label="足球"></el-checkbox>
  13.             <el-checkbox label="篮球"></el-checkbox>
  14.             <el-checkbox label="电竞"></el-checkbox>
  15.             <el-checkbox label="其他"></el-checkbox>
  16.           </el-checkbox-group>
  17.         </el-form-item>
  18.         
  19.         <el-form-item label="直播经验" prop="experience">
  20.           <el-select v-model="form.experience" placeholder="请选择">
  21.             <el-option label="1个月" value="1"></el-option>
  22.             <el-option label="3个月" value="3"></el-option>
  23.             <el-option label="6个月" value="6"></el-option>
  24.             <el-option label="1年" value="12"></el-option>
  25.             <el-option label="2年" value="24"></el-option>
  26.             <el-option label="3年以上" value="36"></el-option>
  27.           </el-select>
  28.         </el-form-item>
  29.         
  30.         <!-- 个人信息 -->
  31.         <el-form-item label="姓名" prop="realName">
  32.           <el-input v-model="form.realName" placeholder="请输入您的真实姓名"></el-input>
  33.         </el-form-item>
  34.         
  35.         <el-form-item label="身份证号" prop="idCard">
  36.           <el-input v-model="form.idCard" placeholder="请输入您身份证号码"></el-input>
  37.         </el-form-item>
  38.         
  39.         <el-form-item label="身份照片" prop="idCardPhoto">
  40.           <el-upload
  41.             action="/api/upload/idCard"
  42.             :limit="1"
  43.             :on-success="handleIdCardSuccess"
  44.             :before-upload="beforeIdCardUpload"
  45.             :file-list="idCardFileList">
  46.             <el-button size="small" type="primary">点击上传</el-button>
  47.             <div slot="tip" class="el-upload__tip">请上传身份证正面照片,支持jpg/png格式,大小不超过5MB</div>
  48.           </el-upload>
  49.         </el-form-item>
  50.         
  51.         <!-- 联系方式 -->
  52.         <el-form-item label="联系方式">
  53.           <el-input v-model="form.qq" placeholder="QQ号"></el-input>
  54.           <el-input v-model="form.wechat" placeholder="微信号"></el-input>
  55.           <el-input v-model="form.phone" placeholder="电话号码"></el-input>
  56.         </el-form-item>
  57.         
  58.         <!-- 个人简介 -->
  59.         <el-form-item label="个人简介" prop="introduction">
  60.           <el-input
  61.             type="textarea"
  62.             :rows="5"
  63.             v-model="form.introduction"
  64.             placeholder="请填写您的个人简介,不少于15字"
  65.             :minlength="15">
  66.           </el-input>
  67.           <div class="intro-tips">
  68.             <p>建议包含以下内容:</p>
  69.             <p>1. 主播经验几年</p>
  70.             <p>2. 在哪些平台上担任过主播</p>
  71.             <p>3. 曾经获得过哪些荣誉</p>
  72.             <p>4. 擅长直播哪类赛事(足球、篮球、电竞、娱乐、其他)</p>
  73.           </div>
  74.         </el-form-item>
  75.         
  76.         <!-- 邀请人 -->
  77.         <el-form-item label="邀请人">
  78.           <el-input v-model="form.inviter" placeholder="请输入邀请人(选填)"></el-input>
  79.           <el-input v-model="form.inviterPhone" placeholder="+86 请输入邀请人电话(选填)"></el-input>
  80.         </el-form-item>
  81.         
  82.         <!-- 验证码 -->
  83.         <el-form-item label="验证码" prop="captcha">
  84.           <el-input v-model="form.captcha" placeholder="请输入验证码" style="width: 200px"></el-input>
  85.           <el-button @click="getCaptcha" :disabled="captchaDisabled">
  86.             {{ captchaBtnText }}
  87.           </el-button>
  88.         </el-form-item>
  89.         
  90.         <!-- 协议 -->
  91.         <el-form-item prop="agreed">
  92.           <el-checkbox v-model="form.agreed">
  93.             我已阅读并同意<a href="/protocol/anchor" target="_blank">《体育直播协议》</a>
  94.           </el-checkbox>
  95.         </el-form-item>
  96.         
  97.         <!-- 提交按钮 -->
  98.         <el-form-item>
  99.           <el-button type="primary" @click="submitForm" :loading="submitting">提交认证</el-button>
  100.         </el-form-item>
  101.       </el-form>
  102.     </div>
  103.    
  104.     <div class="footer">
  105.       <div class="links">
  106.         <a href="#">关于我们</a>
  107.         <a href="#">联系方式</a>
  108.         <a href="#">帮助中心</a>
  109.       </div>
  110.       <div class="copyright">
  111.         Copyright © 2020-2025 龙牙直播 ALL Rights Reserved
  112.       </div>
  113.     </div>
  114.   </div>
  115. </template>
  116. <script>
  117. export default {
  118.   data() {
  119.     return {
  120.       form: {
  121.         liveType: [],
  122.         experience: '',
  123.         realName: '',
  124.         idCard: '',
  125.         idCardPhoto: '',
  126.         qq: '',
  127.         wechat: '',
  128.         phone: '',
  129.         introduction: '',
  130.         inviter: '',
  131.         inviterPhone: '',
  132.         captcha: '',
  133.         agreed: false
  134.       },
  135.       rules: {
  136.         liveType: [
  137.           { type: 'array', required: true, message: '请至少选择一项直播赛事', trigger: 'change' }
  138.         ],
  139.         experience: [
  140.           { required: true, message: '请选择直播经验', trigger: 'change' }
  141.         ],
  142.         realName: [
  143.           { required: true, message: '请输入真实姓名', trigger: 'blur' }
  144.         ],
  145.         idCard: [
  146.           { required: true, message: '请输入身份证号码', trigger: 'blur' },
  147.           { pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '身份证号格式不正确' }
  148.         ],
  149.         idCardPhoto: [
  150.           { required: true, message: '请上传身份证照片', trigger: 'blur' }
  151.         ],
  152.         introduction: [
  153.           { required: true, message: '请输入个人简介', trigger: 'blur' },
  154.           { min: 15, message: '个人简介不能少于15个字符', trigger: 'blur' }
  155.         ],
  156.         captcha: [
  157.           { required: true, message: '请输入验证码', trigger: 'blur' }
  158.         ],
  159.         agreed: [
  160.           { validator: (rule, value, callback) => {
  161.               if (!value) {
  162.                 callback(new Error('请阅读并同意协议'));
  163.               } else {
  164.                 callback();
  165.               }
  166.             }, trigger: 'change' }
  167.         ]
  168.       },
  169.       idCardFileList: [],
  170.       captchaDisabled: false,
  171.       captchaBtnText: '获取验证码',
  172.       countdown: 60,
  173.       submitting: false
  174.     };
  175.   },
  176.   methods: {
  177.     handleIdCardSuccess(response, file) {
  178.       this.form.idCardPhoto = response.data.url;
  179.     },
  180.     beforeIdCardUpload(file) {
  181.       const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
  182.       const isLt5M = file.size / 1024 / 1024 < 5;
  183.       
  184.       if (!isJPG) {
  185.         this.$message.error('上传头像图片只能是 JPG/PNG 格式!');
  186.       }
  187.       if (!isLt5M) {
  188.         this.$message.error('上传头像图片大小不能超过 5MB!');
  189.       }
  190.       return isJPG && isLt5M;
  191.     },
  192.     getCaptcha() {
  193.       if (!this.form.phone) {
  194.         this.$message.error('请输入手机号码');
  195.         return;
  196.       }
  197.       
  198.       // 调用API获取验证码
  199.       this.$http.post('/api/captcha/sms', { phone: this.form.phone })
  200.         .then(() => {
  201.           this.$message.success('验证码已发送');
  202.           this.startCountdown();
  203.         })
  204.         .catch(error => {
  205.           this.$message.error(error.message || '验证码发送失败');
  206.         });
  207.     },
  208.     startCountdown() {
  209.       this.captchaDisabled = true;
  210.       this.captchaBtnText = `${this.countdown}秒后重新获取`;
  211.       
  212.       const timer = setInterval(() => {
  213.         this.countdown -= 1;
  214.         this.captchaBtnText = `${this.countdown}秒后重新获取`;
  215.         
  216.         if (this.countdown <= 0) {
  217.           clearInterval(timer);
  218.           this.captchaDisabled = false;
  219.           this.captchaBtnText = '获取验证码';
  220.           this.countdown = 60;
  221.         }
  222.       }, 1000);
  223.     },
  224.     submitForm() {
  225.       this.$refs.form.validate(valid => {
  226.         if (valid) {
  227.           this.submitting = true;
  228.          
  229.           this.$http.post('/api/anchor/certification', this.form)
  230.             .then(response => {
  231.               this.$message.success('提交成功,请等待审核');
  232.               this.$router.push('/certification/result');
  233.             })
  234.             .catch(error => {
  235.               this.$message.error(error.message || '提交失败');
  236.             })
  237.             .finally(() => {
  238.               this.submitting = false;
  239.             });
  240.         } else {
  241.           return false;
  242.         }
  243.       });
  244.     }
  245.   }
  246. };
  247. </script>
  248. <style scoped>
  249. .anchor-certification {
  250.   max-width: 800px;
  251.   margin: 0 auto;
  252.   padding: 20px;
  253. }
  254. .header {
  255.   text-align: center;
  256.   margin-bottom: 30px;
  257. }
  258. .form-container {
  259.   background: #fff;
  260.   padding: 30px;
  261.   border-radius: 5px;
  262.   box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  263. }
  264. .intro-tips {
  265.   color: #999;
  266.   font-size: 12px;
  267.   margin-top: 5px;
  268. }
  269. .footer {
  270.   margin-top: 30px;
  271.   text-align: center;
  272.   color: #999;
  273.   font-size: 12px;
  274. }
  275. .links {
  276.   margin-bottom: 10px;
  277. }
  278. .links a {
  279.   margin: 0 10px;
  280.   color: #999;
  281.   text-decoration: none;
  282. }
  283. .links a:hover {
  284.   color: #409EFF;
  285. }
  286. </style>
复制代码
2、后端实现 (PHP/ThinkPHP)

  1. <?php
  2. namespace app\api\controller;
  3. use think\Controller;
  4. use think\Request;
  5. use think\Validate;
  6. class AnchorCertification extends Controller
  7. {
  8.     // 提交主播认证
  9.     public function submit()
  10.     {
  11.         $request = Request::instance();
  12.         $data = $request->post();
  13.         
  14.         // 验证规则
  15.         $rules = [
  16.             'liveType' => 'require|array',
  17.             'experience' => 'require',
  18.             'realName' => 'require|chs',
  19.             'idCard' => 'require|idCard',
  20.             'idCardPhoto' => 'require|url',
  21.             'introduction' => 'require|min:15',
  22.             'captcha' => 'require',
  23.             'agreed' => 'require|accepted'
  24.         ];
  25.         
  26.         // 验证消息
  27.         $messages = [
  28.             'liveType.require' => '请选择直播赛事',
  29.             'experience.require' => '请选择直播经验',
  30.             'realName.require' => '请输入真实姓名',
  31.             'realName.chs' => '姓名只能为中文',
  32.             'idCard.require' => '请输入身份证号码',
  33.             'idCard.idCard' => '身份证号格式不正确',
  34.             'idCardPhoto.require' => '请上传身份证照片',
  35.             'idCardPhoto.url' => '身份证照片URL格式不正确',
  36.             'introduction.require' => '请输入个人简介',
  37.             'introduction.min' => '个人简介不能少于15个字符',
  38.             'captcha.require' => '请输入验证码',
  39.             'agreed.require' => '请阅读并同意协议',
  40.             'agreed.accepted' => '请阅读并同意协议'
  41.         ];
  42.         
  43.         $validate = new Validate($rules, $messages);
  44.         
  45.         if (!$validate->check($data)) {
  46.             return json([
  47.                 'code' => 400,
  48.                 'message' => $validate->getError()
  49.             ]);
  50.         }
  51.         
  52.         // 验证验证码
  53.         if (!$this->checkCaptcha($data['phone'], $data['captcha'])) {
  54.             return json([
  55.                 'code' => 400,
  56.                 'message' => '验证码错误或已过期'
  57.             ]);
  58.         }
  59.         
  60.         // 保存认证信息
  61.         $certification = [
  62.             'user_id' => $request->userId, // 从token中获取的用户ID
  63.             'live_type' => implode(',', $data['liveType']),
  64.             'experience' => $data['experience'],
  65.             'real_name' => $data['realName'],
  66.             'id_card' => $data['idCard'],
  67.             'id_card_photo' => $data['idCardPhoto'],
  68.             'qq' => $data['qq'] ?? '',
  69.             'wechat' => $data['wechat'] ?? '',
  70.             'phone' => $data['phone'] ?? '',
  71.             'introduction' => $data['introduction'],
  72.             'inviter' => $data['inviter'] ?? '',
  73.             'inviter_phone' => $data['inviterPhone'] ?? '',
  74.             'status' => 0, // 0-待审核 1-已通过 2-已拒绝
  75.             'create_time' => time(),
  76.             'update_time' => time()
  77.         ];
  78.         
  79.         try {
  80.             $id = db('anchor_certification')->insertGetId($certification);
  81.             
  82.             return json([
  83.                 'code' => 200,
  84.                 'message' => '提交成功,请等待审核',
  85.                 'data' => ['id' => $id]
  86.             ]);
  87.         } catch (\Exception $e) {
  88.             return json([
  89.                 'code' => 500,
  90.                 'message' => '提交失败: ' . $e->getMessage()
  91.             ]);
  92.         }
  93.     }
  94.    
  95.     // 获取验证码
  96.     public function sendCaptcha()
  97.     {
  98.         $request = Request::instance();
  99.         $phone = $request->post('phone');
  100.         
  101.         if (empty($phone)) {
  102.             return json([
  103.                 'code' => 400,
  104.                 'message' => '请输入手机号码'
  105.             ]);
  106.         }
  107.         
  108.         if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
  109.             return json([
  110.                 'code' => 400,
  111.                 'message' => '手机号码格式不正确'
  112.             ]);
  113.         }
  114.         
  115.         // 生成验证码
  116.         $captcha = mt_rand(100000, 999999);
  117.         
  118.         // 保存验证码到缓存,有效期5分钟
  119.         cache('captcha_' . $phone, $captcha, 300);
  120.         
  121.         // TODO: 实际项目中这里应该调用短信服务发送验证码
  122.         // $this->sendSms($phone, $captcha);
  123.         
  124.         return json([
  125.             'code' => 200,
  126.             'message' => '验证码已发送'
  127.         ]);
  128.     }
  129.    
  130.     // 验证验证码
  131.     private function checkCaptcha($phone, $captcha)
  132.     {
  133.         $cacheCaptcha = cache('captcha_' . $phone);
  134.         
  135.         if ($cacheCaptcha && $cacheCaptcha == $captcha) {
  136.             // 验证成功后删除验证码
  137.             cache('captcha_' . $phone, null);
  138.             return true;
  139.         }
  140.         
  141.         return false;
  142.     }
  143.    
  144.     // 上传身份证照片
  145.     public function uploadIdCard()
  146.     {
  147.         $file = request()->file('file');
  148.         
  149.         if (empty($file)) {
  150.             return json([
  151.                 'code' => 400,
  152.                 'message' => '请选择上传文件'
  153.             ]);
  154.         }
  155.         
  156.         // 验证文件类型和大小
  157.         $info = $file->validate([
  158.             'size' => 5242880, // 5MB
  159.             'ext' => 'jpg,jpeg,png'
  160.         ])->move(ROOT_PATH . 'public' . DS . 'uploads' . DS . 'idcards');
  161.         
  162.         if ($info) {
  163.             $url = '/uploads/idcards/' . $info->getSaveName();
  164.             
  165.             return json([
  166.                 'code' => 200,
  167.                 'message' => '上传成功',
  168.                 'data' => ['url' => $url]
  169.             ]);
  170.         } else {
  171.             return json([
  172.                 'code' => 400,
  173.                 'message' => $file->getError()
  174.             ]);
  175.         }
  176.     }
  177. }
复制代码
3、安卓实现 (Java)

  1. // AnchorCertificationActivity.java
  2. public class AnchorCertificationActivity extends AppCompatActivity {
  3.     private EditText etRealName, etIdCard, etQQ, etWechat, etPhone, etIntroduction, etInviter, etInviterPhone, etCaptcha;
  4.     private CheckBox cbFootball, cbBasketball, cbEsports, cbOther;
  5.     private Spinner spExperience;
  6.     private ImageView ivIdCard;
  7.     private Button btnGetCaptcha, btnSubmit;
  8.     private CheckBox cbAgree;
  9.    
  10.     private String idCardPhotoUrl;
  11.     private int countdown = 60;
  12.     private boolean isCountdownRunning = false;
  13.    
  14.     @Override
  15.     protected void onCreate(Bundle savedInstanceState) {
  16.         super.onCreate(savedInstanceState);
  17.         setContentView(R.layout.activity_anchor_certification);
  18.         
  19.         initViews();
  20.         setupSpinner();
  21.     }
  22.    
  23.     private void initViews() {
  24.         etRealName = findViewById(R.id.et_real_name);
  25.         etIdCard = findViewById(R.id.et_id_card);
  26.         etQQ = findViewById(R.id.et_qq);
  27.         etWechat = findViewById(R.id.et_wechat);
  28.         etPhone = findViewById(R.id.et_phone);
  29.         etIntroduction = findViewById(R.id.et_introduction);
  30.         etInviter = findViewById(R.id.et_inviter);
  31.         etInviterPhone = findViewById(R.id.et_inviter_phone);
  32.         etCaptcha = findViewById(R.id.et_captcha);
  33.         
  34.         cbFootball = findViewById(R.id.cb_football);
  35.         cbBasketball = findViewById(R.id.cb_basketball);
  36.         cbEsports = findViewById(R.id.cb_esports);
  37.         cbOther = findViewById(R.id.cb_other);
  38.         
  39.         spExperience = findViewById(R.id.sp_experience);
  40.         
  41.         ivIdCard = findViewById(R.id.iv_id_card);
  42.         ivIdCard.setOnClickListener(v -> uploadIdCard());
  43.         
  44.         btnGetCaptcha = findViewById(R.id.btn_get_captcha);
  45.         btnGetCaptcha.setOnClickListener(v -> getCaptcha());
  46.         
  47.         btnSubmit = findViewById(R.id.btn_submit);
  48.         btnSubmit.setOnClickListener(v -> submitCertification());
  49.         
  50.         cbAgree = findViewById(R.id.cb_agree);
  51.     }
  52.    
  53.     private void setupSpinner() {
  54.         ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
  55.                 R.array.experience_options, android.R.layout.simple_spinner_item);
  56.         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  57.         spExperience.setAdapter(adapter);
  58.     }
  59.    
  60.     private void uploadIdCard() {
  61.         Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  62.         intent.setType("image/*");
  63.         startActivityForResult(intent, 1);
  64.     }
  65.    
  66.     @Override
  67.     protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  68.         super.onActivityResult(requestCode, resultCode, data);
  69.         
  70.         if (requestCode == 1 && resultCode == RESULT_OK && data != null) {
  71.             Uri uri = data.getData();
  72.             try {
  73.                 Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
  74.                 ivIdCard.setImageBitmap(bitmap);
  75.                
  76.                 // 实际上传逻辑
  77.                 uploadImageToServer(uri);
  78.             } catch (IOException e) {
  79.                 e.printStackTrace();
  80.                 Toast.makeText(this, "图片加载失败", Toast.LENGTH_SHORT).show();
  81.             }
  82.         }
  83.     }
  84.    
  85.     private void uploadImageToServer(Uri uri) {
  86.         // 这里实现实际上传逻辑,使用OkHttp或其他网络库
  87.         // 上传成功后保存返回的URL到idCardPhotoUrl
  88.     }
  89.    
  90.     private void getCaptcha() {
  91.         String phone = etPhone.getText().toString().trim();
  92.         
  93.         if (phone.isEmpty()) {
  94.             Toast.makeText(this, "请输入手机号码", Toast.LENGTH_SHORT).show();
  95.             return;
  96.         }
  97.         
  98.         if (!Patterns.PHONE.matcher(phone).matches()) {
  99.             Toast.makeText(this, "手机号码格式不正确", Toast.LENGTH_SHORT).show();
  100.             return;
  101.         }
  102.         
  103.         // 调用API获取验证码
  104.         getCaptchaFromServer(phone);
  105.         
  106.         // 开始倒计时
  107.         startCountdown();
  108.     }
  109.    
  110.     private void getCaptchaFromServer(String phone) {
  111.         // 使用Retrofit或Volley调用API
  112.         // 成功回调后显示提示
  113.         Toast.makeText(this, "验证码已发送", Toast.LENGTH_SHORT).show();
  114.     }
  115.    
  116.     private void startCountdown() {
  117.         isCountdownRunning = true;
  118.         btnGetCaptcha.setEnabled(false);
  119.         
  120.         new CountDownTimer(60000, 1000) {
  121.             @Override
  122.             public void onTick(long millisUntilFinished) {
  123.                 btnGetCaptcha.setText(countdown + "秒后重新获取");
  124.                 countdown--;
  125.             }
  126.             
  127.             @Override
  128.             public void onFinish() {
  129.                 btnGetCaptcha.setEnabled(true);
  130.                 btnGetCaptcha.setText("获取验证码");
  131.                 countdown = 60;
  132.                 isCountdownRunning = false;
  133.             }
  134.         }.start();
  135.     }
  136.    
  137.     private void submitCertification() {
  138.         String realName = etRealName.getText().toString().trim();
  139.         String idCard = etIdCard.getText().toString().trim();
  140.         String introduction = etIntroduction.getText().toString().trim();
  141.         String captcha = etCaptcha.getText().toString().trim();
  142.         
  143.         // 验证必填项
  144.         if (realName.isEmpty()) {
  145.             Toast.makeText(this, "请输入真实姓名", Toast.LENGTH_SHORT).show();
  146.             return;
  147.         }
  148.         
  149.         if (idCard.isEmpty() || !validateIdCard(idCard)) {
  150.             Toast.makeText(this, "请输入正确的身份证号码", Toast.LENGTH_SHORT).show();
  151.             return;
  152.         }
  153.         
  154.         if (idCardPhotoUrl == null || idCardPhotoUrl.isEmpty()) {
  155.             Toast.makeText(this, "请上传身份证照片", Toast.LENGTH_SHORT).show();
  156.             return;
  157.         }
  158.         
  159.         if (introduction.isEmpty() || introduction.length() < 15) {
  160.             Toast.makeText(this, "个人简介不能少于15字", Toast.LENGTH_SHORT).show();
  161.             return;
  162.         }
  163.         
  164.         if (captcha.isEmpty()) {
  165.             Toast.makeText(this, "请输入验证码", Toast.LENGTH_SHORT).show();
  166.             return;
  167.         }
  168.         
  169.         if (!cbAgree.isChecked()) {
  170.             Toast.makeText(this, "请阅读并同意协议", Toast.LENGTH_SHORT).show();
  171.             return;
  172.         }
  173.         
  174.         // 收集直播类型
  175.         List<String> liveTypes = new ArrayList<>();
  176.         if (cbFootball.isChecked()) liveTypes.add("足球");
  177.         if (cbBasketball.isChecked()) liveTypes.add("篮球");
  178.         if (cbEsports.isChecked()) liveTypes.add("电竞");
  179.         if (cbOther.isChecked()) liveTypes.add("其他");
  180.         
  181.         if (liveTypes.isEmpty()) {
  182.             Toast.makeText(this, "请至少选择一项直播赛事", Toast.LENGTH_SHORT).show();
  183.             return;
  184.         }
  185.         
  186.         // 收集其他信息
  187.         String experience = spExperience.getSelectedItem().toString();
  188.         String qq = etQQ.getText().toString().trim();
  189.         String wechat = etWechat.getText().toString().trim();
  190.         String phone = etPhone.getText().toString().trim();
  191.         String inviter = etInviter.getText().toString().trim();
  192.         String inviterPhone = etInviterPhone.getText().toString().trim();
  193.         
  194.         // 提交到服务器
  195.         submitToServer(liveTypes, experience, realName, idCard, idCardPhotoUrl,
  196.                 qq, wechat, phone, introduction, inviter, inviterPhone, captcha);
  197.     }
  198.    
  199.     private boolean validateIdCard(String idCard) {
  200.         // 简单的身份证验证
  201.         return idCard.matches("(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$");
  202.     }
  203.    
  204.     private void submitToServer(List<String> liveTypes, String experience, String realName,
  205.             String idCard, String idCardPhoto, String qq, String wechat, String phone,
  206.             String introduction, String inviter, String inviterPhone, String captcha) {
  207.         
  208.         // 使用Retrofit或Volley提交数据
  209.         // 显示加载中
  210.         ProgressDialog progressDialog = new ProgressDialog(this);
  211.         progressDialog.setMessage("提交中...");
  212.         progressDialog.setCancelable(false);
  213.         progressDialog.show();
  214.         
  215.         // 构造请求体
  216.         JSONObject requestBody = new JSONObject();
  217.         try {
  218.             requestBody.put("liveType", new JSONArray(liveTypes));
  219.             requestBody.put("experience", experience);
  220.             requestBody.put("realName", realName);
  221.             requestBody.put("idCard", idCard);
  222.             requestBody.put("idCardPhoto", idCardPhoto);
  223.             requestBody.put("qq", qq);
  224.             requestBody.put("wechat", wechat);
  225.             requestBody.put("phone", phone);
  226.             requestBody.put("introduction", introduction);
  227.             requestBody.put("inviter", inviter);
  228.             requestBody.put("inviterPhone", inviterPhone);
  229.             requestBody.put("captcha", captcha);
  230.             requestBody.put("agreed", true);
  231.         } catch (JSONException e) {
  232.             e.printStackTrace();
  233.         }
  234.         
  235.         // 实际网络请求
  236.         // 成功回调
  237.         progressDialog.dismiss();
  238.         Toast.makeText(this, "提交成功,请等待审核", Toast.LENGTH_SHORT).show();
  239.         finish();
  240.     }
  241. }
复制代码
4、iOS实现 (Objective-C)

  1. // AnchorCertificationViewController.h
  2. #import <UIKit/UIKit.h>
  3. @interface AnchorCertificationViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate>
  4. @end
  5. // AnchorCertificationViewController.m
  6. #import "AnchorCertificationViewController.h"
  7. @interface AnchorCertificationViewController ()
  8. @property (weak, nonatomic) IBOutlet UITextField *realNameField;
  9. @property (weak, nonatomic) IBOutlet UITextField *idCardField;
  10. @property (weak, nonatomic) IBOutlet UITextField *qqField;
  11. @property (weak, nonatomic) IBOutlet UITextField *wechatField;
  12. @property (weak, nonatomic) IBOutlet UITextField *phoneField;
  13. @property (weak, nonatomic) IBOutlet UITextView *introductionView;
  14. @property (weak, nonatomic) IBOutlet UITextField *inviterField;
  15. @property (weak, nonatomic) IBOutlet UITextField *inviterPhoneField;
  16. @property (weak, nonatomic) IBOutlet UITextField *captchaField;
  17. @property (weak, nonatomic) IBOutlet UIButton *footballBtn;
  18. @property (weak, nonatomic) IBOutlet UIButton *basketballBtn;
  19. @property (weak, nonatomic) IBOutlet UIButton *esportsBtn;
  20. @property (weak, nonatomic) IBOutlet UIButton *otherBtn;
  21. @property (weak, nonatomic) IBOutlet UIPickerView *experiencePicker;
  22. @property (weak, nonatomic) IBOutlet UIImageView *idCardImageView;
  23. @property (weak, nonatomic) IBOutlet UIButton *getCaptchaBtn;
  24. @property (weak, nonatomic) IBOutlet UIButton *submitBtn;
  25. @property (weak, nonatomic) IBOutlet UIButton *agreeBtn;
  26. @property (strong, nonatomic) NSArray *experienceOptions;
  27. @property (strong, nonatomic) NSString *idCardPhotoUrl;
  28. @property (assign, nonatomic) NSInteger countdown;
  29. @property (strong, nonatomic) NSTimer *countdownTimer;
  30. @end
  31. @implementation AnchorCertificationViewController
  32. - (void)viewDidLoad {
  33.     [super viewDidLoad];
  34.    
  35.     [self setupUI];
  36.     [self setupData];
  37. }
  38. - (void)setupUI {
  39.     self.introductionView.layer.borderWidth = 1.0;
  40.     self.introductionView.layer.borderColor = [UIColor lightGrayColor].CGColor;
  41.     self.introductionView.layer.cornerRadius = 5.0;
  42.    
  43.     self.idCardImageView.userInteractionEnabled = YES;
  44.     UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(uploadIdCard)];
  45.     [self.idCardImageView addGestureRecognizer:tap];
  46.    
  47.     self.experiencePicker.delegate = self;
  48.     self.experiencePicker.dataSource = self;
  49. }
  50. - (void)setupData {
  51.     self.experienceOptions = @[@"1个月", @"3个月", @"6个月", @"1年", @"2年", @"3年以上"];
  52.     self.countdown = 60;
  53. }
  54. - (IBAction)liveTypeSelected:(UIButton *)sender {
  55.     sender.selected = !sender.selected;
  56. }
  57. - (IBAction)getCaptchaTapped:(id)sender {
  58.     NSString *phone = self.phoneField.text;
  59.    
  60.     if (phone.length == 0) {
  61.         [self showAlertWithTitle:@"提示" message:@"请输入手机号码"];
  62.         return;
  63.     }
  64.    
  65.     // 简单的手机号验证
  66.     NSString *phoneRegex = @"^1[3-9]\\d{9}$";
  67.     NSPredicate *phoneTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", phoneRegex];
  68.     if (![phoneTest evaluateWithObject:phone]) {
  69.         [self showAlertWithTitle:@"提示" message:@"手机号码格式不正确"];
  70.         return;
  71.     }
  72.    
  73.     [self getCaptchaFromServer:phone];
  74.     [self startCountdown];
  75. }
  76. - (void)getCaptchaFromServer:(NSString *)phone {
  77.     // 实际项目中这里应该调用API获取验证码
  78.     [self showAlertWithTitle:@"提示" message:@"验证码已发送"];
  79. }
  80. - (void)startCountdown {
  81.     self.getCaptchaBtn.enabled = NO;
  82.     self.countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateCountdown) userInfo:nil repeats:YES];
  83. }
  84. - (void)updateCountdown {
  85.     if (self.countdown > 0) {
  86.         [self.getCaptchaBtn setTitle:[NSString stringWithFormat:@"%ld秒后重新获取", (long)self.countdown] forState:UIControlStateDisabled];
  87.         self.countdown--;
  88.     } else {
  89.         [self.countdownTimer invalidate];
  90.         self.countdownTimer = nil;
  91.         self.getCaptchaBtn.enabled = YES;
  92.         [self.getCaptchaBtn setTitle:@"获取验证码" forState:UIControlStateNormal];
  93.         self.countdown = 60;
  94.     }
  95. }
  96. - (void)uploadIdCard {
  97.     UIImagePickerController *picker = [[UIImagePickerController alloc] init];
  98.     picker.delegate = self;
  99.     picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  100.     picker.allowsEditing = YES;
  101.     [self presentViewController:picker animated:YES completion:nil];
  102. }
  103. - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
  104.     UIImage *image = info[UIImagePickerControllerEditedImage] ?: info[UIImagePickerControllerOriginalImage];
  105.     self.idCardImageView.image = image;
  106.    
  107.     // 实际上传逻辑
  108.     [self uploadImageToServer:image];
  109.    
  110.     [picker dismissViewControllerAnimated:YES completion:nil];
  111. }
  112. - (void)uploadImageToServer:(UIImage *)image {
  113.     // 这里实现实际上传逻辑,使用AFNetworking或其他网络库
  114.     // 上传成功后保存返回的URL到self.idCardPhotoUrl
  115. }
  116. - (IBAction)submitTapped:(id)sender {
  117.     NSString *realName = self.realNameField.text;
  118.     NSString *idCard = self.idCardField.text;
  119.     NSString *introduction = self.introductionView.text;
  120.     NSString *captcha = self.captchaField.text;
  121.    
  122.     // 验证必填项
  123.     if (realName.length == 0) {
  124.         [self showAlertWithTitle:@"提示" message:@"请输入真实姓名"];
  125.         return;
  126.     }
  127.    
  128.     if (idCard.length == 0 || ![self validateIdCard:idCard]) {
  129.         [self showAlertWithTitle:@"提示" message:@"请输入正确的身份证号码"];
  130.         return;
  131.     }
  132.    
  133.     if (self.idCardPhotoUrl.length == 0) {
  134.         [self showAlertWithTitle:@"提示" message:@"请上传身份证照片"];
  135.         return;
  136.     }
  137.    
  138.     if (introduction.length < 15) {
  139.         [self showAlertWithTitle:@"提示" message:@"个人简介不能少于15字"];
  140.         return;
  141.     }
  142.    
  143.     if (captcha.length == 0) {
  144.         [self showAlertWithTitle:@"提示" message:@"请输入验证码"];
  145.         return;
  146.     }
  147.    
  148.     if (!self.agreeBtn.selected) {
  149.         [self showAlertWithTitle:@"提示" message:@"请阅读并同意协议"];
  150.         return;
  151.     }
  152.    
  153.     // 收集直播类型
  154.     NSMutableArray *liveTypes = [NSMutableArray array];
  155.     if (self.footballBtn.selected) [liveTypes addObject:@"足球"];
  156.     if (self.basketballBtn.selected) [liveTypes addObject:@"篮球"];
  157.     if (self.esportsBtn.selected) [liveTypes addObject:@"电竞"];
  158.     if (self.otherBtn.selected) [liveTypes addObject:@"其他"];
  159.    
  160.     if (liveTypes.count == 0) {
  161.         [self showAlertWithTitle:@"提示" message:@"请至少选择一项直播赛事"];
  162.         return;
  163.     }
  164.    
  165.     // 收集其他信息
  166.     NSInteger selectedRow = [self.experiencePicker selectedRowInComponent:0];
  167.     NSString *experience = self.experienceOptions[selectedRow];
  168.     NSString *qq = self.qqField.text;
  169.     NSString *wechat = self.wechatField.text;
  170.     NSString *phone = self.phoneField.text;
  171.     NSString *inviter = self.inviterField.text;
  172.     NSString *inviterPhone = self.inviterPhoneField.text;
  173.    
  174.     // 提交到服务器
  175.     [self submitToServerWithLiveTypes:liveTypes experience:experience realName:realName
  176.                              idCard:idCard idCardPhoto:self.idCardPhotoUrl qq:qq
  177.                            wechat:wechat phone:phone introduction:introduction
  178.                           inviter:inviter inviterPhone:inviterPhone captcha:captcha];
  179. }
  180. - (BOOL)validateIdCard:(NSString *)idCard {
  181.     NSString *regex = @"(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$";
  182.     NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
  183.     return [predicate evaluateWithObject:idCard];
  184. }
  185. - (void)submitToServerWithLiveTypes:(NSArray *)liveTypes experience:(NSString *)experience
  186.                           realName:(NSString *)realName idCard:(NSString *)idCard
  187.                       idCardPhoto:(NSString *)idCardPhoto qq:(NSString *)qq
  188.                           wechat:(NSString *)wechat phone:(NSString *)phone
  189.                      introduction:(NSString *)introduction inviter:(NSString *)inviter
  190.                     inviterPhone:(NSString *)inviterPhone captcha:(NSString *)captcha {
  191.    
  192.     // 显示加载中
  193.     UIAlertController *loadingAlert = [UIAlertController alertControllerWithTitle:nil message:@"提交中..." preferredStyle:UIAlertControllerStyleAlert];
  194.     [self presentViewController:loadingAlert animated:YES completion:nil];
  195.    
  196.     // 构造请求参数
  197.     NSDictionary *params = @{
  198.         @"liveType": liveTypes,
  199.         @"experience": experience,
  200.         @"realName": realName,
  201.         @"idCard": idCard,
  202.         @"idCardPhoto": idCardPhoto,
  203.         @"qq": qq ?: @"",
  204.         @"wechat": wechat ?: @"",
  205.         @"phone": phone ?: @"",
  206.         @"introduction": introduction,
  207.         @"inviter": inviter ?: @"",
  208.         @"inviterPhone": inviterPhone ?: @"",
  209.         @"captcha": captcha,
  210.         @"agreed": @YES
  211.     };
  212.    
  213.     // 实际网络请求
  214.     // 使用AFNetworking或NSURLSession
  215.     // 成功回调
  216.     [loadingAlert dismissViewControllerAnimated:YES completion:^{
  217.         [self showAlertWithTitle:@"提示" message:@"提交成功,请等待审核"];
  218.       
复制代码
5、数据库设计 (MySQL)

  1. CREATE TABLE `anchor_certification` (
  2.   `id` INT PRIMARY KEY AUTO_INCREMENT,
  3.   `name` VARCHAR(50) NOT NULL COMMENT '主播真实姓名',
  4.   `identity_card` VARCHAR(18) NOT NULL COMMENT '身份证号码',
  5.   `identity_photo_front` VARCHAR(255) NOT NULL COMMENT '身份证正面照片路径',
  6.   `identity_photo_back` VARCHAR(255) NOT NULL COMMENT '身份证反面照片路径',
  7.   `identity_photo_handheld` VARCHAR(255) NOT NULL COMMENT '手持身份证照片路径',
  8.   `contact_type` ENUM('QQ', '微信', '电话') DEFAULT NULL COMMENT '联系方式类型',
  9.   `contact_value` VARCHAR(50) DEFAULT NULL COMMENT '联系方式内容',
  10.   `live_experience` ENUM('1个月', '3个月', '1年', '3年', '5年以上') NOT NULL COMMENT '直播经验',
  11.   `live_type` SET('足球', '篮球', '电竞', '娱乐', '其他') NOT NULL COMMENT '擅长的直播类型',
  12.   `personal_intro` TEXT NOT NULL COMMENT '个人简介',
  13.   `invite_code` VARCHAR(20) DEFAULT NULL COMMENT '邀请人邀请码',
  14.   `status` ENUM('待审核', '已通过', '已拒绝') DEFAULT '待审核' COMMENT '审核状态',
  15.   `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  16.   `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
  17. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主播认证信息表';
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

盛世宏图

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表